diff --git a/02_卡人(水)/水桥_平台对接/Soul创业实验/写作/写作规范.md b/02_卡人(水)/水桥_平台对接/Soul创业实验/写作/写作规范.md index c501ab6b..724e1426 100644 --- a/02_卡人(水)/水桥_平台对接/Soul创业实验/写作/写作规范.md +++ b/02_卡人(水)/水桥_平台对接/Soul创业实验/写作/写作规范.md @@ -8,10 +8,10 @@ | 项目 | 规范 | |:---|:---| -| **「我」** | **整篇文章最多出现三次**(仅指叙述者视角;对话引用里的「我」不占名额)。成稿后全文搜索「我」,超过 3 处必须改写。 | -| **「这边」「那边」** | **不要用**。改用灵活表述:房主、场上、就、手头、这种模式、知识星球/小程序 等指代或省略主语。 | -| **「回答说」** | **不要用**。房主是强势角色,文章目的是让读者产生深度认同感;房主的话用**直接陈述**呈现,不写「回答说,……」,让语气与性格一致、话一出口就立住。 | -| **角色与语气** | **叙述者/房主角色偏强势**:说话形式、语气稍强势——干脆、笃定,少用「可能」「也许」「或者可以」等弱化表述,多用断句和直接结论,让读者有认同感。 | +| **「我」** | 叙述者用「我」,**不要写「房主」**;对话里被问到时用「我」即可。成稿后检查:凡原可用房主处一律改为我。 | +| **「这边」「那边」** | **不要用**。改用灵活表述:我、场上、就、手头、这种模式、知识星球/小程序 等指代或省略主语。 | +| **「回答说」** | **不要用**。叙述者是强势角色,文章目的是让读者产生深度认同感;我的话用**直接陈述**呈现,不写「回答说,……」,让语气与性格一致、话一出口就立住。 | +| **角色与语气** | **叙述者角色偏强势**:说话形式、语气稍强势——干脆、笃定,少用「可能」「也许」「或者可以」等弱化表述,多用断句和直接结论,让读者有认同感。 | | **「卡若」** | 每篇最多提一次;不需要时可完全不出现。 | --- @@ -20,7 +20,7 @@ | 项目 | 规范 | |:---|:---| -| 字数 | 每小节 2000~5000 字 | +| 字数 | 每小节 3000~5000 字 | | 来源 | 以真实聊天内容为基础,不改原意 | | 数据 | 以聊天内提到的数值为主,不编造(场观、人数、时长、营收等);**有数值就必须写具体**,不写「大概很多」「不少」等模糊说法 | | **数值与场景** | **数值跟场景要具体写进去**:金额、人数、时长、比例、曝光量、进房数、成本、获客价等,一律写清具体数字;涉及 Soul 投流、派对价值时,要写清算法口径(如:约 75~80 曝光进 1 人、每天约 3 万曝光、进房 300~600 人、1000 曝光 6~10 块、进一人约 4 毛 2、获客到微信约 20 块/人等) | @@ -51,14 +51,14 @@ - **推进方式**:时间线或事件线,逻辑清晰(如「有人问 → 回答」「3 号问 → …」) - **分段**:每段一个主题,小主题隐于叙述中,不列段头小标题 - **穿插**:细节、对话、观点分析 -- **多用对话**:增强真实感(「X 号问」「有人问」等);房主的话**直接接在问句后**,不写「回答说」,**语气偏强势**、干脆笃定、可认同。 +- **多用对话**:增强真实感(「X 号问」「有人问」等);我的话**直接接在问句后**,不写「回答说」,**语气偏强势**、干脆笃定、可认同。 - **分享句(两处,强制)**:约 20% 处一句、结尾一句,各不超过 50 字,围绕本节主题、紧扣内容,留余味或可执行。**不要出现「干货」二字**,不要用「干货:」或「**干货**:」等格式,直接写一句金句即可,可单独成段。 --- ## 六、写作技巧 -- 第一人称叙述时少用「我」,见第一节(整篇最多三次) +- 第一人称叙述用「我」,不用「房主」,见第一节 - **短句,大白话,口语化**:像平时说话一样写,不书面、不拽词 - **每句空一行**:一句写完就换行、再空一行再写下一句,不要好几句挤成一段 - 善用对比、反转;适当自嘲或幽默 diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/今日进度_39.png b/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/今日进度_39.png new file mode 100644 index 00000000..79f4fe85 Binary files /dev/null and b/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/今日进度_39.png differ diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json index f7c26a17..6c6e11c2 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json @@ -1,6 +1,6 @@ { - "access_token": "u-elKtEXTr14dr_RNRKQJh6Plh1I31ghgjW0GaYAg0231V", - "refresh_token": "ur-d79XTR1w55DGDdJ2C8aGijlh3A11ghOXh0GaURg022lU", + "access_token": "u-dNsovXz6Z23G8q5DaAUYOBlh3A31ghgNXwGaYN0027hE", + "refresh_token": "ur-eHGm3TE658MbdAGK5fO8lVlh1IH1ghMpP0GaER00230Z", "name": "飞书用户", "auth_time": "2026-03-09T05:48:48.008966" } \ No newline at end of file diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py index d36bad21..78a420b0 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py @@ -53,11 +53,13 @@ ROWS = { '117': [ '链接合作 派对流量 教培', 154, 51602, 503, 11, 118, 16, 351, 36, 72 ], # 118场 2026-03-07:关闭页 190min/586成员/65最高/33新增/46礼物/7456灵魂力/74873曝光,小助手 180min/559进房/10人均/149互动/29关注 '118': [ '3D打印 游戏代充 正财偏财', 190, 74873, 586, 10, 149, 46, 7456, 33, 65 ], + # 119场 2026-03-08:关闭页 154min/446成员/64最高/26新增/20礼物/3315灵魂力/50692曝光,小助手 151min/441进房/11人均/110互动/23关注 + '119': [ '开派对初心 早上不影响老婆', 154, 50692, 446, 11, 110, 20, 3315, 26, 64 ], } # 场次→按日期列填写时的日期(表头为当月日期 1~31) -SESSION_DATE_COLUMN = {'105': '20', '106': '21', '107': '23', '113': '2', '114': '3', '115': '4', '116': '5', '117': '6', '118': '7'} +SESSION_DATE_COLUMN = {'105': '20', '106': '21', '107': '23', '113': '2', '114': '3', '115': '4', '116': '5', '117': '6', '118': '7', '119': '8'} # 场次→月份(用于选择 2月/3月 等工作表标签,避免写入错月) -SESSION_MONTH = {'105': 2, '106': 2, '107': 2, '113': 3, '114': 3, '115': 3, '116': 3, '117': 3, '118': 3} +SESSION_MONTH = {'105': 2, '106': 2, '107': 2, '113': 3, '114': 3, '115': 3, '116': 3, '117': 3, '118': 3, '119': 3} # 派对录屏(飞书妙记)链接:场次 → 完整 URL,填表时写入「派对录屏」行对应列 # 从飞书妙记复制链接后填入,新场次需补全 @@ -68,6 +70,7 @@ PARTY_VIDEO_LINKS = { '116': 'https://cunkebao.feishu.cn/minutes/obcn81825en52vt3eqoo482e', '117': 'https://cunkebao.feishu.cn/minutes/obcn9phnds9a96ma6t8ixa3z', '118': 'https://cunkebao.feishu.cn/minutes/obcnaee1h83l1s169e3a18qp', + '119': 'https://cunkebao.feishu.cn/minutes/obcnbrc925796a6u4c667931', } # 小程序当日运营数据:日期号 → {访问次数, 访客, 交易金额},填表时自动写入对应日期列 @@ -86,6 +89,7 @@ MINIPROGRAM_EXTRA_3 = { '5': {'访问次数': 0, '访客': 0, '交易金额': 0}, # 3月5日 116场 '6': {'访问次数': 0, '访客': 0, '交易金额': 0}, # 3月6日 117场 '7': {'访问次数': 0, '访客': 0, '交易金额': 0}, # 3月7日 118场 + '8': {'访问次数': 0, '访客': 0, '交易金额': 0}, # 3月8日 119场 } @@ -349,7 +353,7 @@ def main(): session = (sys.argv[1] if len(sys.argv) > 1 else '104').strip() row = ROWS.get(session) if not row: - print('❌ 未知场次,可用: 96, 97, 98, 99, 100, 103, 104, 105, 106, 107, 113, 114, 115, 116, 117, 118') + print('❌ 未知场次,可用: 96, 97, 98, 99, 100, 103, 104, 105, 106, 107, 113, 114, 115, 116, 117, 118, 119') sys.exit(1) token = load_token() or refresh_and_load_token() if not token: @@ -394,9 +398,9 @@ def main(): LABELS_GROUP = ['主题', '时长(分钟)', 'Soul推流人数', '进房人数', '人均时长(分钟)', '互动数量', '礼物', '灵魂力', '增加关注', '最高在线'] def _maybe_send_group(sess, raw_vals): - if sess not in ('105', '106', '107', '113', '114', '115', '116', '117', '118'): + if sess not in ('105', '106', '107', '113', '114', '115', '116', '117', '118', '119'): return - date_label = {'105': '2月20日', '106': '2月21日', '107': '2月23日', '113': '3月2日', '114': '3月3日', '115': '3月4日', '116': '3月5日', '117': '3月6日', '118': '3月7日'}.get(sess, sess + '场') + date_label = {'105': '2月20日', '106': '2月21日', '107': '2月23日', '113': '3月2日', '114': '3月3日', '115': '3月4日', '116': '3月5日', '117': '3月6日', '118': '3月7日', '119': '3月8日'}.get(sess, sess + '场') report_link = OPERATION_REPORT_LINK if sheet_id == SHEET_ID else f'https://cunkebao.feishu.cn/wiki/wikcnIgAGSNHo0t36idHJ668Gfd?sheet={sheet_id}' lines = [ '【Soul 派对运营报表】', @@ -407,7 +411,7 @@ def main(): for i, label in enumerate(LABELS_GROUP): val = raw_vals[i] if i < len(raw_vals) else '' lines.append(f'{label}:{val}') - src_date = {'105': '20260220', '106': '20260221', '107': '20260223', '113': '20260302', '114': '20260303', '115': '20260304', '116': '20260305', '117': '20260306', '118': '20260307'}.get(sess, '20260220') + src_date = {'105': '20260220', '106': '20260221', '107': '20260223', '113': '20260302', '114': '20260303', '115': '20260304', '116': '20260305', '117': '20260306', '118': '20260307', '119': '20260308'}.get(sess, '20260220') lines.append(f'数据来源:soul 派对 {sess}场 {src_date}.txt') msg = '\n'.join(lines) ok, _ = send_feishu_group_message(FEISHU_GROUP_WEBHOOK, msg) diff --git a/02_卡人(水)/水溪_整理归档/经验库/待沉淀/2026-03-09_113场视频切片与成片经验.md b/02_卡人(水)/水溪_整理归档/经验库/待沉淀/2026-03-09_113场视频切片与成片经验.md new file mode 100644 index 00000000..f831843c --- /dev/null +++ b/02_卡人(水)/水溪_整理归档/经验库/待沉淀/2026-03-09_113场视频切片与成片经验.md @@ -0,0 +1,94 @@ +# 113场视频切片与成片经验 + +## 一、适用场景 + +- Soul 派对长视频,需要先提取高光时间,再切片,再生成可发布成片。 +- 用户要求成片可直接发布,必须包含: + - 清晰标题 + - 对应封面 + - 字幕烧录 + - 去语助词 + - 加速 10% + - 竖屏裁剪 498×1080 + +## 二、本次有效流程 + +1. 先确认原视频总时长与章节原文。 +2. 用 `MLX Whisper` 生成完整 `transcript.srt`。 +3. 结合章节文稿与转录稿,提取 `highlights.json`。 +4. 用 `batch_clip.py` 按时间节点切出 `切片/`。 +5. 用 `soul_enhance.py --vertical --title-only --force-burn-subs` 生成 `成片/`。 + +## 三、113场的关键经验 + +### 1. 高光时间不能只靠均分 + +- 113 场之前做过等分切片,但这不是真高光。 +- 真正可用的做法是: + - 先读章节文稿 + - 再看转录里的关键词锚点 + - 最后手动或半自动整理成 `highlights.json` +- 对长视频,建议高光段数控制在 **10~30 段**。 + +### 2. 转录稿可能“前面脏、后面干净” + +- 113 场开头有重复噪声,例如连续的短句重复。 +- 不能因为开头几分钟异常,就把整份 `transcript.srt` 判成坏稿。 +- 正确做法: + - 允许局部脏数据存在 + - 只要主体转录可用,就继续跑高光和字幕 + +### 3. 字幕误判规则不能太激进 + +- 原来的 `_is_bad_transcript()` 会把短句较多的正常口语段误判成“整篇同一句”。 +- 已修正为: + - 只统计 **长度 >= 4** 的字幕 + - 同时看重复比例和去重后占比 +- 这样可以避免把正常问答段错误跳过字幕烧录。 + +### 4. 成片必须统一结构 + +- 文件名 = 封面标题 = `highlights.json.title` +- 前 3 秒优先用 `question`,没有提问时用 `hook_3sec` +- 成片统一执行: + - 封面 + - 字幕烧录 + - 去语助词 + - 加速 10% + - 竖屏裁剪 + +## 四、实操命令 + +```bash +# 1. 切片 +python3 batch_clip.py \ + -i "/Users/karuo/Movies/soul视频/原视频/soul 派对 113场 20260302.mp4" \ + -l "/Users/karuo/Movies/soul视频/soul 派对 113场 20260302_output/highlights.json" \ + -o "/Users/karuo/Movies/soul视频/soul 派对 113场 20260302_output/切片" \ + -p soul113 + +# 2. 成片 +python3 soul_enhance.py \ + -c "/Users/karuo/Movies/soul视频/soul 派对 113场 20260302_output/切片" \ + -l "/Users/karuo/Movies/soul视频/soul 派对 113场 20260302_output/highlights.json" \ + -t "/Users/karuo/Movies/soul视频/soul 派对 113场 20260302_output/transcript.srt" \ + -o "/Users/karuo/Movies/soul视频/soul 派对 113场 20260302_output/成片" \ + --vertical --title-only --force-burn-subs +``` + +## 五、后续固定执行规则 + +- 以后 Soul 长视频默认按这套流程走: + - 章节文稿定结构 + - 转录稿定时间 + - `highlights.json` 做中控 + - `batch_clip.py` 出切片 + - `soul_enhance.py` 出成片 +- 遇到“字幕没烧进去”,先查两件事: + 1. `transcript.srt` 是否完整 + 2. `_is_bad_transcript()` 是否误判 + +## 六、结论 + +- 113 场这次的关键不是“怎么切”,而是“怎么让结构、标题、封面、字幕真正对齐”。 +- 以后凡是 Soul 场次,只要有章节文稿,就优先按“章节结构 + 转录时间”的方式提高光,不再先做均分切片。 diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/SKILL.md b/03_卡木(木)/木叶_视频内容/抖音发布/SKILL.md index 939b4baa..060ccb77 100644 --- a/03_卡木(木)/木叶_视频内容/抖音发布/SKILL.md +++ b/03_卡木(木)/木叶_视频内容/抖音发布/SKILL.md @@ -62,8 +62,9 @@ python3 douyin_publish.py --video "/path/to/xxx.mp4" --title "标题" --token-fi ## 五、与视频切片 / Soul 竖屏的联动 - **成片目录**:Soul 竖屏成片输出在 `xxx_output/成片/`,文件名为标题(如 `没人来就一个人站站到最后钱才来.mp4`)。 -- **批量发布**:可对 `成片/` 目录遍历,逐条调用 `douyin_publish.py --video --title <文件名或 highlights 标题>`;标题可来自 `成片/目录索引.md` 或 `highlights.json`。 -- **腕推 / 存客宝**:若使用腕推或存客宝的抖音发布能力,可将对接方式(API 文档、SDK 路径)补充到本 Skill 的「参考资料」或脚本说明中,脚本可改为调其接口。 +- **批量发布**:可对 `成片/` 目录遍历,逐条调用 `douyin_publish.py --video --title <文件名或 highlights 标题>`;标题可来自 `成片/目录索引.md` 或 `highlights.json`。示例:119 场成片可用 `脚本/batch_publish_119.py`(成片目录需与脚本内一致),发布清单见成片目录下 `119场_抖音发布清单.md`。 +- **腕推 / 存客宝 / 卡罗维亚**:若使用腕推、卡罗维亚或存客宝的抖音发布能力,可将对接方式(API 文档、SDK 路径)补充到本 Skill 的「参考资料」或脚本说明中,脚本可改为调其接口;**标题与描述**可直接使用每批成片目录下的「发布清单」复制进对应工具。 +- **小黄车与挂载**:开放平台 create_video 支持挂载小程序;电商小黄车需在抖音端发布后编辑挂载或使用创作者中心。详见 `参考资料/抖音开放平台_登录与发布流程.md` 第四节。 --- diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/参考资料/抖音开放平台_登录与发布流程.md b/03_卡木(木)/木叶_视频内容/抖音发布/参考资料/抖音开放平台_登录与发布流程.md index aae893b0..ff08371a 100644 --- a/03_卡木(木)/木叶_视频内容/抖音发布/参考资料/抖音开放平台_登录与发布流程.md +++ b/03_卡木(木)/木叶_视频内容/抖音发布/参考资料/抖音开放平台_登录与发布流程.md @@ -40,7 +40,13 @@ 文档:https://partner.open-douyin.com/docs/resource/zh-CN/dop/develop/openapi/video-management/douyin/create-video/video-create -## 四、存客宝 / 腕推 +## 四、小黄车与挂载(标题/描述/带货) + +- **标题与描述**:每条成片建议标题、描述、话题见当批「发布清单」(如成片目录下的 `119场_抖音发布清单.md`),可直接用于抖音 App、创作者中心、或复制到**卡罗维亚/腕推**等矩阵工具内填写。 +- **create_video 挂载**:开放平台 `create_video` 接口支持挂载**小程序**(需传 `micro_app_id`、`micro_app_url`、`micro_app_title`,且需申请对应能力),不支持直接挂电商「小黄车」商品。 +- **小黄车/带货**:若需挂商品,可在发布后于抖音 App 内对该条视频「编辑」→「添加商品」挂载;或使用已开通抖音电商/商品橱窗的创作者中心在发布时勾选商品。卡罗维亚等工具若支持「默认挂载商品」或「发布后自动挂载」,以该工具说明为准。 + +## 五、存客宝 / 腕推 / 卡罗维亚 - 当前**存客宝** Skill 与文档中无抖音登录或发布 SDK。 -- 若使用**腕推**或其它矩阵工具发布抖音,请将对接方式(API 或 SDK 文档路径)补充到「抖音发布」Skill 或本参考资料,脚本可改为调用对应接口。 +- 若使用**腕推**、**卡罗维亚**或其它矩阵工具发布抖音,请将对接方式(API 或 SDK 文档路径)补充到「抖音发布」Skill 或本参考资料,脚本可改为调用对应接口。发布清单(标题、描述、话题)可直接复制到该类工具使用。 diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/all_post_requests.json b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/all_post_requests.json new file mode 100644 index 00000000..a09fd8b2 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/all_post_requests.json @@ -0,0 +1,191 @@ +[ + { + "url": "https://creator.douyin.com/aweme/v1/open/publish/limit_app_groups/?device_platform=pc&cookie_enabled=true&screen_width=1280&screen_height=720&browser_language=zh-CN&browser_platform=MacIntel&browser_name=Mozilla&browser_version=5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F143.0.0.0+Safari%2F537.36&browser_online=true&timezone_name=Asia%2FShanghai&aid=1128&support_h265=1&msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZO", + "method": "POST", + "content_type": "" + }, + { + "url": "https://creator.douyin.com/web/api/media/aweme/create_v2/?read_aid=2906&cookie_enabled=true&screen_width=1280&screen_height=720&browser_language=zh-CN&browser_platform=MacIntel&browser_name=Mozilla&browser_version=5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F143.0.0.0+Safari%2F537.36&browser_online=true&timezone_name=Asia%2FShanghai&aid=1128&support_h265=1&msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_", + "method": "POST", + "content_type": "application/json", + "body": "{\"item\":{\"common\":{\"text\":\"广点通能投Soul了 测试发布\",\"caption\":\"广点通能投Soul了 测试发布\",\"item_title\":\"\",\"activity\":\"[]\",\"text_extra\":\"[]\",\"challenges\":\"[]\",\"mentions\":\"[]\",\"hashtag_source\":\"\",\"hot_sentence\":\"\",\"interaction_stickers\":\"[]\",\"visibility_type\":0,\"download\":1,\"timing\":0,\"creation_id\":\"86o77cjp1773058000249\",\"media_type\":4,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"music_source\":0,\"music_id\":null},\"cover\":{\"custom_cover_image_height\":663,\"custom_cover_image_width\":497,\"poster\":\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\",\"poster_delay\":30.08,\"horizontal_custom_cover_image_uri\":\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\",\"horizontal_cover_tsp\":30.08,\"horizontal_custom_cover_image_height\":663,\"horizontal_custom_cover_image_width\":498,\"cover_tools_extend_info\":\"{\\\"recommendServerInfo\\\":{\\\"res\\\":[],\\\"times\\\":[]},\\\"recommendCoverList\\\":[{\\\"id\\\":\\\"7615586833bf667e27abd5345f67d8a4\\\",\\\"time\\\":20.08,\\\"uri\\\":\\\"NOT_READY\\\",\\\"frameIndex\\\":2,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/3d78919a-9166-480b-b901-aa8bb45afa65\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b094f7a6-6907-4843-ad73-c9b6956e39bf\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":true,\\\"uri1\\\":\\\"NOT_READY\\\"},{\\\"id\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"time\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"frameIndex\\\":3,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/a6eb4869-fb2b-4bc3-b20f-438783edd70d\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b15c59c4-deae-4ccd-a73f-86368d7f7b91\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\"},{\\\"id\\\":\\\"e8a806ba18960a6c0b925f25fdb65a03\\\",\\\"time\\\":40.08,\\\"uri\\\":\\\"NOT_READY\\\",\\\"frameIndex\\\":4,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/feb0d50b-a38a-49b0-acc4-0d0ff3b6153a\\\",\\\"cropHeight\\\":663.3667498826981,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.06012023985385895,1,0.6743487119674683],\\\"cropBox2\\\":[0,0.06012023985385895,1,0.6743487119674683],\\\"src\\\":\\\"blob:https://creator.douyin.com/079df6cf-5d25-4a22-bf2a-0b088b9ebb29\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"NOT_READY\\\"}],\\\"recommendCoverInfo\\\":{\\\"isFromRecommend\\\":true,\\\"isDefaultSelect\\\":false,\\\"isRecommendClickFrom\\\":\\\"outer\\\",\\\"selectInfo\\\":{\\\"id\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"time\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"frameIndex\\\":3,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/a6eb4869-fb2b-4bc3-b20f-438783edd70d\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b15c59c4-deae-4ccd-a73f-86368d7f7b91\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\"},\\\"editingInfo\\\":{},\\\"isStartEditing\\\":false},\\\"recommendCoverTime\\\":0,\\\"coverInfo\\\":{\\\"videoName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"coverEditLogInfo\\\":{\\\"is_cover_edit\\\":\\\"true\\\",\\\"cover_type\\\":\\\"recommend_pic\\\",\\\"is_setting_double_cover\\\":1,\\\"video_cover_source\\\":\\\"recommend_pic\\\",\\\"is_text_template\\\":0,\\\"is_text\\\":0,\\\"text_num\\\":0,\\\"is_cover_template\\\":0,\\\"tab_name\\\":\\\"\\\",\\\"sticker_tab_name\\\":\\\"\\\",\\\"filter_tab_name\\\":\\\"\\\",\\\"text_content\\\":\\\"\\\",\\\"text_template_content\\\":\\\"\\\",\\\"is_use_sticker\\\":0,\\\"is_use_filter\\\":0,\\\"text_template_id\\\":\\\"\\\",\\\"sticker_id\\\":\\\"\\\",\\\"filter_id\\\":[],\\\"second_cover_details\\\":\\\"{\\\\\\\"video_cover_source_landscape\\\\\\\":\\\\\\\"recommend_pic\\\\\\\",\\\\\\\"is_text_template_landscape\\\\\\\":0,\\\\\\\"is_text_landscape\\\\\\\":0,\\\\\\\"text_num_landscape\\\\\\\":0,\\\\\\\"is_cover_template_landscape\\\\\\\":0,\\\\\\\"tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_template_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"is_use_sticker_landscape\\\\\\\":0,\\\\\\\"is_use_filter_landscape\\\\\\\":0,\\\\\\\"text_template_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_id_landscape\\\\\\\":[]}\\\"},\\\"posterDelay\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"customCoverImageHeight\\\":663.3667176961899,\\\"customCoverImageWidth\\\":497.5250382721424,\\\"draft\\\":{\\\"slot\\\":\\\"tos://tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"width\\\":498,\\\"height\\\":663.3667176961899,\\\"templateId\\\":\\\"7293420016291151922\\\",\\\"templateUrl\\\":\\\"https://lf3-media.amemv.com/obj/media-platform-open/2e82e90e-d8d2-4664-9a22-eb21f304ef3a\\\"},\\\"edited\\\":true,\\\"type\\\":2,\\\"de", + "body_length": 13500, + "headers": { + "sec-ch-ua-platform": "\"macOS\"", + "x-tt-session-dtrait": "d0_FkGIwcjOBGVkrreJRhLMj2GAyT6Xh2dtRDWR7oUmwfguthLgGBzbrmWG6zu8RqxhLXdkq8roKxki0M6CjaDD33Q96YrILE+7XO4ddA6GMvaefrzzUwjl5TOJ9P6q1nL7VUa16mlQBB39mDL1Qvcru6dq+9bJ6oi9ufRx6xx/SPUG23zk1oUSIQDhP84nhgms/UC75zGM/hIZ6P1F7lnd3fKUE+MT4mhgU4Mb76ysns8krOix20WF26prHvrF7OU8lVuA09V3rNGTqStxPOVdia+NOpvWkKHrqKwm5bzJ0ch2tHo1EDPrfMO3lU3vysKid/OjwWcmPFlZg+cQSq2uhw==_LmJaprOUloKcVq69zGaoEgteH/tSqpVDFnMHLcj4s6LBKLZE4X7Y7463URLdwJ+D8RwJqo1opXIMXfGzI+fq0xXJVuQGVBPLlETq4PqlurlIXvhgx8XN1DBed6SRnkZJZaWNm+XxNcrBRwPUMy/FQmQi5pkBKtoxN6AabVXHr2fHkjYonylqFqcdzGU6sTKLqySWa0KbqKMY2WjRFb37MgtU3YGM3voLG6vGkNj7akeN2/bP5tyoavhTbmmWt5/Ij7u4jYIrj/oFOPcduxalNvWvVhiPnuK1irkH0DhYviFpsrq95c0bSR4Jhz72ocjgbfiMtsYIth0RDxtplaWIyqSYfVsDYayOOKmppVx3/Dnn/0n1wJ32TKOGAF4iXo/LsJTOwivnv+pXRX9nVmlywR9OlC2rnyCaGetYwab5GpvaT9z5fPYuQByX/z8bSNog8/eMLt+0Ta2wDM2PvX2w34D65aipjK430RUfxHtTV9U=", + "referer": "https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page", + "sec-ch-ua": "\"Chromium\";v=\"143\", \"Not A(Brand\";v=\"24\"", + "bd-ticket-guard-web-version": "2", + "sec-ch-ua-mobile": "?0", + "bd-ticket-guard-client-data": "eyJ0c19zaWduIjoidHMuMi4xNzVhNjY0NjYyYWZmNThhZmU3Y2I5MTgyMWIxOGM0MWEyNmFmNTU4ZWQwMzYxMjEyMGE0NmEyMjFlNzU1ZWUzYzRmYmU4N2QyMzE5Y2YwNTMxODYyNGNlZGExNDkxMWNhNDA2ZGVkYmViZWRkYjJlMzBmY2U4ZDRmYTAyNTc1ZCIsInJlcV9jb250ZW50IjoidGlja2V0LHBhdGgsdGltZXN0YW1wIiwicmVxX3NpZ24iOiJudzd3Zlh0bzYwbUc0Uk91OWZ5dEp0VElFZWxPS2VEamxCRVNrZWoxUm04PSIsInRpbWVzdGFtcCI6MTc3MzA1ODAyN30=", + "bd-ticket-guard-web-sign-type": "1", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36", + "accept": "application/json, text/plain, */*", + "x-secsdk-csrf-token": "000100000001a22775e255397d6311ea08cde8025d64f8a45c0f7df7f717e049fde934a1dde8189b2adaa85a6ba0", + "content-type": "application/json", + "bd-ticket-guard-ree-public-key": "BPNB/e+d134tog5uPa6LVRibIdxycNTFav5Bn8NyXatdz3EZGs3CsCyPD8bkGXTHK0mCs5PaqAyXkAXNEnIkwkw=", + "bd-ticket-guard-version": "2" + }, + "full_body": "{\"item\":{\"common\":{\"text\":\"广点通能投Soul了 测试发布\",\"caption\":\"广点通能投Soul了 测试发布\",\"item_title\":\"\",\"activity\":\"[]\",\"text_extra\":\"[]\",\"challenges\":\"[]\",\"mentions\":\"[]\",\"hashtag_source\":\"\",\"hot_sentence\":\"\",\"interaction_stickers\":\"[]\",\"visibility_type\":0,\"download\":1,\"timing\":0,\"creation_id\":\"86o77cjp1773058000249\",\"media_type\":4,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"music_source\":0,\"music_id\":null},\"cover\":{\"custom_cover_image_height\":663,\"custom_cover_image_width\":497,\"poster\":\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\",\"poster_delay\":30.08,\"horizontal_custom_cover_image_uri\":\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\",\"horizontal_cover_tsp\":30.08,\"horizontal_custom_cover_image_height\":663,\"horizontal_custom_cover_image_width\":498,\"cover_tools_extend_info\":\"{\\\"recommendServerInfo\\\":{\\\"res\\\":[],\\\"times\\\":[]},\\\"recommendCoverList\\\":[{\\\"id\\\":\\\"7615586833bf667e27abd5345f67d8a4\\\",\\\"time\\\":20.08,\\\"uri\\\":\\\"NOT_READY\\\",\\\"frameIndex\\\":2,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/3d78919a-9166-480b-b901-aa8bb45afa65\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b094f7a6-6907-4843-ad73-c9b6956e39bf\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":true,\\\"uri1\\\":\\\"NOT_READY\\\"},{\\\"id\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"time\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"frameIndex\\\":3,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/a6eb4869-fb2b-4bc3-b20f-438783edd70d\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b15c59c4-deae-4ccd-a73f-86368d7f7b91\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\"},{\\\"id\\\":\\\"e8a806ba18960a6c0b925f25fdb65a03\\\",\\\"time\\\":40.08,\\\"uri\\\":\\\"NOT_READY\\\",\\\"frameIndex\\\":4,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/feb0d50b-a38a-49b0-acc4-0d0ff3b6153a\\\",\\\"cropHeight\\\":663.3667498826981,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.06012023985385895,1,0.6743487119674683],\\\"cropBox2\\\":[0,0.06012023985385895,1,0.6743487119674683],\\\"src\\\":\\\"blob:https://creator.douyin.com/079df6cf-5d25-4a22-bf2a-0b088b9ebb29\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"NOT_READY\\\"}],\\\"recommendCoverInfo\\\":{\\\"isFromRecommend\\\":true,\\\"isDefaultSelect\\\":false,\\\"isRecommendClickFrom\\\":\\\"outer\\\",\\\"selectInfo\\\":{\\\"id\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"time\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"frameIndex\\\":3,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/a6eb4869-fb2b-4bc3-b20f-438783edd70d\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b15c59c4-deae-4ccd-a73f-86368d7f7b91\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\"},\\\"editingInfo\\\":{},\\\"isStartEditing\\\":false},\\\"recommendCoverTime\\\":0,\\\"coverInfo\\\":{\\\"videoName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"coverEditLogInfo\\\":{\\\"is_cover_edit\\\":\\\"true\\\",\\\"cover_type\\\":\\\"recommend_pic\\\",\\\"is_setting_double_cover\\\":1,\\\"video_cover_source\\\":\\\"recommend_pic\\\",\\\"is_text_template\\\":0,\\\"is_text\\\":0,\\\"text_num\\\":0,\\\"is_cover_template\\\":0,\\\"tab_name\\\":\\\"\\\",\\\"sticker_tab_name\\\":\\\"\\\",\\\"filter_tab_name\\\":\\\"\\\",\\\"text_content\\\":\\\"\\\",\\\"text_template_content\\\":\\\"\\\",\\\"is_use_sticker\\\":0,\\\"is_use_filter\\\":0,\\\"text_template_id\\\":\\\"\\\",\\\"sticker_id\\\":\\\"\\\",\\\"filter_id\\\":[],\\\"second_cover_details\\\":\\\"{\\\\\\\"video_cover_source_landscape\\\\\\\":\\\\\\\"recommend_pic\\\\\\\",\\\\\\\"is_text_template_landscape\\\\\\\":0,\\\\\\\"is_text_landscape\\\\\\\":0,\\\\\\\"text_num_landscape\\\\\\\":0,\\\\\\\"is_cover_template_landscape\\\\\\\":0,\\\\\\\"tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_template_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"is_use_sticker_landscape\\\\\\\":0,\\\\\\\"is_use_filter_landscape\\\\\\\":0,\\\\\\\"text_template_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_id_landscape\\\\\\\":[]}\\\"},\\\"posterDelay\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"customCoverImageHeight\\\":663.3667176961899,\\\"customCoverImageWidth\\\":497.5250382721424,\\\"draft\\\":{\\\"slot\\\":\\\"tos://tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"width\\\":498,\\\"height\\\":663.3667176961899,\\\"templateId\\\":\\\"7293420016291151922\\\",\\\"templateUrl\\\":\\\"https://lf3-media.amemv.com/obj/media-platform-open/2e82e90e-d8d2-4664-9a22-eb21f304ef3a\\\"},\\\"edited\\\":true,\\\"type\\\":2,\\\"defaultUri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"horizontalDefaultUri\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"cropedUri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"aiGenCoverId\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"url\\\":\\\"https://p0-creator-media-private.douyin.com/tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55~tplv-jm8ajry58r-image.jpeg?policy=eyJ2bSI6MywidWlkIjoiOTU1MTkxOTQ4OTcifQ%3D%3D&rk3s=70809c85&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1773061612&x-orig-sign=dQgmvtx%2FB9Zn1ReTjTqTS%2FRo9mA%3D\\\"},\\\"coverUrl\\\":\\\"https://p0-creator-media-private.douyin.com/tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55~tplv-jm8ajry58r-image.jpeg?policy=eyJ2bSI6MywidWlkIjoiOTU1MTkxOTQ4OTcifQ%3D%3D&rk3s=70809c85&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1773061612&x-orig-sign=dQgmvtx%2FB9Zn1ReTjTqTS%2FRo9mA%3D\\\",\\\"coverHorizontalInfo\\\":{\\\"videoName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"coverEditLogInfo\\\":{\\\"is_cover_edit\\\":\\\"true\\\",\\\"cover_type\\\":\\\"recommend_pic\\\",\\\"is_setting_double_cover\\\":1,\\\"video_cover_source\\\":\\\"recommend_pic\\\",\\\"is_text_template\\\":0,\\\"is_text\\\":0,\\\"text_num\\\":0,\\\"is_cover_template\\\":0,\\\"tab_name\\\":\\\"\\\",\\\"sticker_tab_name\\\":\\\"\\\",\\\"filter_tab_name\\\":\\\"\\\",\\\"text_content\\\":\\\"\\\",\\\"text_template_content\\\":\\\"\\\",\\\"is_use_sticker\\\":0,\\\"is_use_filter\\\":0,\\\"text_template_id\\\":\\\"\\\",\\\"sticker_id\\\":\\\"\\\",\\\"filter_id\\\":[],\\\"second_cover_details\\\":\\\"{\\\\\\\"video_cover_source_landscape\\\\\\\":\\\\\\\"recommend_pic\\\\\\\",\\\\\\\"is_text_template_landscape\\\\\\\":0,\\\\\\\"is_text_landscape\\\\\\\":0,\\\\\\\"text_num_landscape\\\\\\\":0,\\\\\\\"is_cover_template_landscape\\\\\\\":0,\\\\\\\"tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_template_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"is_use_sticker_landscape\\\\\\\":0,\\\\\\\"is_use_filter_landscape\\\\\\\":0,\\\\\\\"text_template_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_id_landscape\\\\\\\":[]}\\\"},\\\"posterDelay\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"customCoverImageHeight\\\":663.3667176961899,\\\"customCoverImageWidth\\\":498,\\\"edited\\\":true,\\\"draft\\\":{\\\"slot\\\":\\\"tos://tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"width\\\":498,\\\"height\\\":663.3667176961899,\\\"templateId\\\":\\\"7293420016291053618\\\",\\\"templateUrl\\\":\\\"https://lf3-media.amemv.com/obj/media-platform-open/4f4f551c-7951-4a29-b3e8-fd628670fc3f\\\"},\\\"type\\\":2,\\\"defaultUri\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"cropedUri\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"aiGenCoverId\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"url\\\":\\\"https://p0-creator-media-private.douyin.com/tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32~tplv-jm8ajry58r-image.jpeg?policy=eyJ2bSI6MywidWlkIjoiOTU1MTkxOTQ4OTcifQ%3D%3D&rk3s=70809c85&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1773061612&x-orig-sign=g5PsdQ6IgpXRgDv1msFdBPHJcrw%3D\\\"},\\\"coverHorizontalUrl\\\":\\\"https://p0-creator-media-private.douyin.com/tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32~tplv-jm8ajry58r-image.jpeg?policy=eyJ2bSI6MywidWlkIjoiOTU1MTkxOTQ4OTcifQ%3D%3D&rk3s=70809c85&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1773061612&x-orig-sign=g5PsdQ6IgpXRgDv1msFdBPHJcrw%3D\\\",\\\"pasterInfo\\\":{},\\\"stateInfo\\\":null,\\\"croppedCoverInfo\\\":null,\\\"uploadBackgroundInfo\\\":null,\\\"uploadPasterInfo\\\":null,\\\"uploadCoverStateInfo\\\":null,\\\"xiguaCoverInfo\\\":{\\\"posterDelay\\\":0},\\\"xiguaPasterInfo\\\":null,\\\"xiguaStateInfo\\\":null,\\\"xiguaUploadCoverStateInfo\\\":null,\\\"xiguaUploadBackgroundInfo\\\":null,\\\"xiguaUploadPasterInfo\\\":null,\\\"editXigua\\\":false,\\\"coverSource\\\":\\\"\\\",\\\"previewVideoList\\\":[{\\\"isCurrent\\\":true},{\\\"coverUrl\\\":\\\"https://p26-sign.douyinpic.com/tos-cn-i-0813c000-ce/ocg0yaxIPomXiAWiNAB9HBMG1AqA6BBqPvAVE~tplv-dy-resize-walign-adapt-aq:540:q75.webp?lk3s=138a59ce&x-expires=1774267200&x-signature=qjYI%2FuKfHuas1XFwVfG2ONb8u08%3D&from=327834062&s=PackSourceEnum_PUBLISH&se=false&sc=cover&biz_tag=aweme_video&l=20260309200640335F4D3FFDF9AD98686F\\\",\\\"isTiming\\\":false,\\\"isPreview\\\":false,\\\"isCurrent\\\":false,\\\"isLiveReplay\\\":false},{\\\"coverUrl\\\":\\\"https://p3-sign.douyinpic.com/tos-cn-p-0015c000-ce/oUSBJwDO2QA60FeNDOqiDeC18gIfaDGrSIS7lL~tplv-dy-resize-walign-adapt-aq:540:q75.webp?lk3s=138a59ce&x-expires=1774267200&x-signature=uPBOZEoLPj8mTBeNFmcYEUlCxnU%3D&from=327834062&s=PackSourceEnum_PUBLISH&se=false&sc=cover&biz_tag=aweme_video&l=20260309200640335F4D3FFDF9AD98686F\\\",\\\"isTiming\\\":false,\\\"isPreview\\\":false,\\\"isCurrent\\\":false,\\\"isLiveReplay\\\":false},{\\\"coverUrl\\\":\\\"https://p26-sign.douyinpic.com/tos-cn-p-0015c000-ce/oUOBcIVchfpAiBqxoDEbBFYA9E2DtwdnyEfN1P~tplv-dy-resize-walign-adapt-aq:540:q75.webp?lk3s=138a59ce&x-expires=1774267200&x-signature=YNSTWNaKoa3M7nyGfKwDeFOVbPo%3D&from=327834062&s=PackSourceEnum_PUBLISH&se=false&sc=cover&biz_tag=aweme_vid" + }, + { + "url": "https://mcs.zijieapi.com/list?aid=1661&sdk_version=5.3.8&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "[{\"events\":[{\"event\":\"web_bd_ticket_dtrait_response\",\"params\":\"{\\\"_staging_flag\\\":0,\\\"sdk_version\\\":\\\"3.3.11\\\",\\\"self_platform\\\":\\\"web\\\",\\\"params_for_special\\\":\\\"uc_login\\\",\\\"hostname\\\":\\\"creator.douyin.com\\\",\\\"centralVersion\\\":\\\"d0\\\",\\\"dTraitVersion\\\":\\\"0\\\",\\\"edgeVersion\\\":\\\"d0\\\",\\\"urlVersion\\\":\\\"1.0.0.16\\\",\\\"dTraitContainerSdkVersion\\\":\\\"1.1.0\\\",\\\"useBuildIn\\\":1,\\\"duration\\\":547,\\\"pathname\\\":\\\"/web/api/media/aweme/create_v2/\\\",\\\"host\\\":\\\"creator.douyin.com\\\",\\\"has_dTraitToken\\\":0,\\\"logid\\\":\\\"20260309200707A755E18E544D6CB65F5F\\\",\\\"http_code\\\":200,\\\"performance_time\\\":33389,\\\"event_index\\\":1773058761593}\",\"local_time_ms\":1773058027639,\"is_bav\":0,\"session_id\":\"e29be62b-8fef-40a3-8f60-b191b08b6d00\"},{\"event\":\"web_bd_ticket_guard_consumer_response\",\"params\":\"{\\\"_staging_flag\\\":0,\\\"sdk_version\\\":\\\"3.3.11\\\",\\\"self_platform\\\":\\\"web\\\",\\\"params_for_special\\\":\\\"uc_login\\\",\\\"logid\\\":\\\"20260309200707A755E18E544D6CB65F5F\\\",\\\"path\\\":\\\"/web/api/media/aweme/create_v2/\\\",\\\"error_code\\\":\\\"-99\\\",\\\"login_status\\\":\\\"1\\\",\\\"sign_version\\\":\\\"0\\\",\\\"cookie_status\\\":\\\"1\\\",\\\"data_from\\\":\\\"0\\\",\\\"cookie_crypt\\\":\\\"1\\\",\\\"match_md5_local\\\":\\\"-99\\\",\\\"match_md5_iframe\\\":\\\"-99\\\",\\\"is_pubkey_ts_sign\\\":\\\"-99\\\",\\\"is_new_cert\\\":\\\"0\\\",\\\"hostname\\\":\\\"creator.douyin.com\\\",\\\"cookie_domain\\\":\\\"2\\\",\\\"event_index\\\":1773058761592}\",\"local_time_ms\":1773058027639,\"is_bav\":0,\"session_id\":\"e29be62b-8fef-40a3-8f60-b191b08b6d00\"}],\"user\":{\"user_unique_id\":\"95519194897\",\"user_id\":\"95519194897\",\"device_id\":\"95519194897\",\"web_id\":\"1515143131341514113\"},\"header\":{\"app_id\":1661,\"os_name\":\"mac\",\"os_version\":\"10_15_7\",\"device_model\":\"Macintosh\",\"language\":\"zh-CN\",\"platform\":\"web\",\"sdk_version\":\"5.3.8\",\"sdk_lib\":\"js\",\"timezone\":8,\"tz_offset\":-28800,\"resolution\":\"1280x720\",\"browser\":\"Chrome\",\"browser_version\":\"143.0.0.0\",\"referrer\":\"\",\"referrer_host\":\"\",\"width\":1280,\"height\":720,\"screen_width\":1280,\"screen_height\":720,\"tracer_data\":\"{\\\"$utm_from_url\\\":1}\",\"custom\":\"{\\\"network_type\\\":\\\"4g\\\"}\"},\"local_time\":1773058027,\"verbose\":1}]", + "body_length": 2003 + }, + { + "url": "https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll?msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=mv0Vg7XimZ%2F5OpMS8OD9t-2USgI%2FrT8yDtT2WrJjtOi1aqtGh3eQTOslJOzQZq0dKRBIked7VdYATnfauUmv0IVkFmpfSgJRu4VIn8XLgqidaMksgqDqCh0zFwsblbkL-ACviA6RIs0wgdQlVHKBAxlGH5FimmbpSqHcdqT9SEW6DWWkw93TOHk2PLTqNE%3D%3D", + "method": "POST", + "content_type": "application/json", + "body": "{\"resource_list\":[{\"type\":2,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"duration\":66.366,\"cover_uri\":\"\"}],\"source\":1,\"is_redetect\":false,\"task_id\":\"7615226012955204398\"}", + "body_length": 172 + }, + { + "url": "https://mcs.zijieapi.com/tobid?aid=1661&sdk_version=5.3.9&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "{\"app_id\":1661,\"user_unique_id\":\"95519194897\",\"web_id\":\"1515143131341514113\"}", + "body_length": 77 + }, + { + "url": "https://mcs.zijieapi.com/list?aid=1661&sdk_version=5.3.9&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "[{\"events\":[{\"event\":\"onload\",\"params\":\"{\\\"app_id\\\":1661,\\\"app_name\\\":\\\"\\\",\\\"sdk_version\\\":\\\"5.3.9\\\",\\\"sdk_type\\\":\\\"npm\\\",\\\"sdk_config\\\":{\\\"app_id\\\":1661,\\\"channel\\\":\\\"cn\\\",\\\"log\\\":0},\\\"sdk_desc\\\":\\\"TOC\\\",\\\"url\\\":\\\"https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page\\\"}\",\"local_time_ms\":1773058028228}],\"user\":{\"user_unique_id\":\"1515143131341514113\"},\"header\":{}}]", + "body_length": 396 + }, + { + "url": "https://mcs.zijieapi.com/list?aid=1661&sdk_version=5.3.9&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "[{\"events\":[{\"event\":\"predefine_pageview\",\"params\":\"{\\\"_staging_flag\\\":0,\\\"verify_aid\\\":2906,\\\"sdk_version\\\":\\\"1.0.0.379\\\",\\\"uc_account_verify_fun\\\":\\\"verify\\\",\\\"uc_account_verify_version\\\":\\\"1.0.19\\\",\\\"title\\\":\\\"抖音创作者中心\\\",\\\"url\\\":\\\"https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page\\\",\\\"url_path\\\":\\\"/creator-micro/content/post/video\\\",\\\"time\\\":1773058028228,\\\"referrer\\\":\\\"\\\",\\\"$is_first_time\\\":\\\"false\\\",\\\"event_index\\\":1773058889889}\",\"local_time_ms\":1773058028228,\"is_bav\":0,\"session_id\":\"488f74af-e455-4f0a-9e03-4ed10daa7a76\"}],\"user\":{\"user_unique_id\":\"95519194897\",\"web_id\":\"1515143131341514113\"},\"header\":{\"app_id\":1661,\"os_name\":\"mac\",\"os_version\":\"10_15_7\",\"device_model\":\"Macintosh\",\"language\":\"zh-CN\",\"platform\":\"web\",\"sdk_version\":\"5.3.9\",\"sdk_lib\":\"js\",\"timezone\":8,\"tz_offset\":-28800,\"resolution\":\"1280x720\",\"browser\":\"Chrome\",\"browser_version\":\"143.0.0.0\",\"referrer\":\"\",\"referrer_host\":\"\",\"width\":1280,\"height\":720,\"screen_width\":1280,\"screen_height\":720,\"tracer_data\":\"{\\\"$utm_from_url\\\":1}\",\"custom\":\"{\\\"network_type\\\":\\\"4g\\\"}\"},\"local_time\":1773058028,\"verbose\":1}]", + "body_length": 1118 + }, + { + "url": "https://mcs.zijieapi.com/list?aid=1661&sdk_version=5.3.8&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "[{\"events\":[{\"event\":\"web_bd_ticket_dtrait_response\",\"params\":\"{\\\"_staging_flag\\\":0,\\\"sdk_version\\\":\\\"3.3.11\\\",\\\"self_platform\\\":\\\"web\\\",\\\"params_for_special\\\":\\\"uc_login\\\",\\\"hostname\\\":\\\"creator.douyin.com\\\",\\\"centralVersion\\\":\\\"d0\\\",\\\"dTraitVersion\\\":\\\"0\\\",\\\"edgeVersion\\\":\\\"d0\\\",\\\"urlVersion\\\":\\\"1.0.0.16\\\",\\\"dTraitContainerSdkVersion\\\":\\\"1.1.0\\\",\\\"useBuildIn\\\":1,\\\"duration\\\":400,\\\"pathname\\\":\\\"/passport/safe/query_decision/\\\",\\\"host\\\":\\\"creator.douyin.com\\\",\\\"has_dTraitToken\\\":0,\\\"logid\\\":\\\"20260309200708264CA7FCEB8223C4AE6F\\\",\\\"http_code\\\":200,\\\"performance_time\\\":34177.90000000596,\\\"event_index\\\":1773058761594}\",\"local_time_ms\":1773058028428,\"is_bav\":0,\"session_id\":\"e29be62b-8fef-40a3-8f60-b191b08b6d00\"}],\"user\":{\"user_unique_id\":\"95519194897\",\"user_id\":\"95519194897\",\"device_id\":\"95519194897\",\"web_id\":\"1515143131341514113\"},\"header\":{\"app_id\":1661,\"os_name\":\"mac\",\"os_version\":\"10_15_7\",\"device_model\":\"Macintosh\",\"language\":\"zh-CN\",\"platform\":\"web\",\"sdk_version\":\"5.3.8\",\"sdk_lib\":\"js\",\"timezone\":8,\"tz_offset\":-28800,\"resolution\":\"1280x720\",\"browser\":\"Chrome\",\"browser_version\":\"143.0.0.0\",\"referrer\":\"\",\"referrer_host\":\"\",\"width\":1280,\"height\":720,\"screen_width\":1280,\"screen_height\":720,\"tracer_data\":\"{\\\"$utm_from_url\\\":1}\",\"custom\":\"{\\\"network_type\\\":\\\"4g\\\"}\"},\"local_time\":1773058028,\"verbose\":1}]", + "body_length": 1320 + }, + { + "url": "https://mcs.zijieapi.com/list?aid=2562&sdk_version=5.3.7&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "[{\"events\":[{\"event\":\"image_999\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":999,\\\"fk\\\":\\\"file_1773058028649_776752\\\",\\\"fs\\\":134675,\\\"ts\\\":1773058029,\\\"dur\\\":0,\\\"tdur\\\":0,\\\"durs\\\":0,\\\"tdurs\\\":0,\\\"skipCommit\\\":0,\\\"experimental\\\":null,\\\"type\\\":\\\"taskStart\\\",\\\"ex\\\":\\\"{\\\\\\\"msg\\\\\\\":\\\\\\\"taskStart 1 preUpload\\\\\\\"}\\\",\\\"event_index\\\":1773058062593}\",\"local_time_ms\":1773058028650,\"is_bav\":0,\"session_id\":\"ae2bd8e2-b70e-4b38-a882-f0eb4090e7c1\"},{\"event\":\"image_1000\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":1000,\\\"fk\\\":\\\"file_1773058028649_776752\\\",\\\"fs\\\":134675,\\\"ts\\\":1773058029,\\\"sst\\\":1773058028650,\\\"set\\\":1773058028650,\\\"dur\\\":0,\\\"tdur\\\":0,\\\"durs\\\":0,\\\"tdurs\\\":0,\\\"skipCommit\\\":0,\\\"experimental\\\":null,\\\"type\\\":\\\"success\\\",\\\"ex\\\":\\\"{\\\\\\\"msg\\\\\\\":\\\\\\\"get crc32 success\\\\\\\",\\\\\\\"crc32Array\\\\\\\":[{\\\\\\\"star\\\\\\\":0,\\\\\\\"end\\\\\\\":134675,\\\\\\\"crc32\\\\\\\":\\\\\\\"539061eb\\\\\\\",\\\\\\\"buffer\\\\\\\":{}}],\\\\\\\"isDirect\\\\\\\":true}\\\",\\\"event_index\\\":1773058062592}\",\"local_time_ms\":1773058028650,\"is_bav\":0,\"session_id\":\"ae2bd8e2-b70e-4b38-a882-f0eb4090e7c1\"},{\"event\":\"image_999\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":999,\\\"fs\\\":134675,\\\"ts\\\":1773058029,\\\"durs\\\":null,\\\"tdurs\\\":null,\\\"skipCommit\\\":0,\\\"experimental\\\":null,\\\"type\\\":\\\"taskStart\\\",\\\"ex\\\":\\\"{\\\\\\\"msg\\\\\\\":\\\\\\\"taskStart 0 crc32\\\\\\\"}\\\",\\\"event_index\\\":1773058062591}\",\"local_time_ms\":1773058028650,\"is_bav\":0,\"session_id\":\"ae2bd8e2-b70e-4b38-a882-f0eb4090e7c1\"},{\"event\":\"image_999\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":999,\\\"fk\\\":\\\"file_1773058028649_776752\\\",\\\"fs\\\":134675,\\\"ts\\\":1773058029,\\\"durs\\\":null,\\\"tdurs\\\":null,\\\"skipCommit\\\":0,\\\"experimental\\\":null,\\\"type\\\":\\\"start\\\",\\\"ex\\\":\\\"{\\\\\\\"msg\\\\\\\":\\\\\\\"start upload 0\\\\\\\"}\\\",\\\"event_index\\\":1773058062590}\",\"local_time_ms\":1773058028649,\"is_bav\":0,\"session_id\":\"ae2bd8e2-b70e-4b38-a882-f0eb4090e7c1\"},{\"event\":\"image_999\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":999,\\\"fs\\\":134675,\\\"ts\\\":1773058029,\\\"durs\\\":null,\\\"tdurs\\\":null,\\\"skipCommit\\\":0,\\\"experimental\\\":null,\\\"type\\\":\\\"allTaskList\\\",\\\"ex\\\":\\\"{\\\\\\\"msg\\\\\\\":\\\\\\\"-0-1-3-5\\\\\\\"}\\\",\\\"event_index\\\":1773058062589}\",\"local_time_ms\":1773058028649,\"is_bav\":0,\"session_id\":\"ae2bd8e2-b70e-4b38-a882-f0eb4090e7c1\"}],\"user\":{\"user_unique_id\":\"95519194897\",\"web_id\":\"7615225443776448000\"},\"header\":{\"app_id\":2562,\"os_name\":\"mac\",\"os_version\":\"10_15_7\",\"device_model\":\"Macintosh\",\"language\":\"zh-CN\",\"platform\":\"web\",\"sdk_version\":\"5.3.7\",\"sdk_lib\":\"js\",\"timezone\":8,\"tz_offset\":-28800,\"resolution\":\"1280x720\",\"browser\":\"Chrome\",\"browser_version\":\"143.0.0.0\",\"referrer\":\"\",\"referrer_host\":\"\",\"width\":1280,\"height\":720,\"screen_width\":1280,\"screen_height\":720,\"tracer_data\":\"{\\\"$utm_from_url\\\":1}\",\"custom\":\"{\\\"network_type\\\":\\\"4g\\\"}\"},\"local_time\":1773058028,\"verbose\":1}]", + "body_length": 3027 + }, + { + "url": "https://tos-lf-x.snssdk.com/upload/v1/tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6", + "method": "POST", + "content_type": "application/octet-stream", + "body": null + }, + { + "url": "https://mcs.zijieapi.com/list?aid=2562&sdk_version=5.3.7&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "[{\"events\":[{\"event\":\"image_999\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":999,\\\"fk\\\":\\\"file_1773058028649_776752\\\",\\\"fs\\\":134675,\\\"ts\\\":1773058029,\\\"sst\\\":1773058028650,\\\"set\\\":1773058029003,\\\"dur\\\":353,\\\"tdur\\\":353,\\\"durs\\\":0.353,\\\"tdurs\\\":0.353,\\\"ucsrr\\\":false,\\\"skipCommit\\\":0,\\\"experimental\\\":0,\\\"type\\\":\\\"taskStart\\\",\\\"oid\\\":\\\"tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\",\\\"upload_host\\\":\\\"https://tos-lf-x.snssdk.com\\\",\\\"ex\\\":\\\"{\\\\\\\"msg\\\\\\\":\\\\\\\"taskStart 3 process\\\\\\\"}\\\",\\\"event_index\\\":1773058062595}\",\"local_time_ms\":1773058029003,\"is_bav\":0,\"session_id\":\"ae2bd8e2-b70e-4b38-a882-f0eb4090e7c1\"},{\"event\":\"image_1001\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":1001,\\\"fk\\\":\\\"file_1773058028649_776752\\\",\\\"fs\\\":134675,\\\"req\\\":{\\\"url\\\":\\\"https://imagex.bytedanceapi.com/?Action=ApplyImageUpload&Version=2018-08-01&ServiceId=jm8ajry58r&app_id=2906&user_id=95519194897&s=cvz3nfqmton\\\",\\\"param\\\":{}},\\\"res\\\":{\\\"status\\\":200,\\\"header\\\":\\\"content-type: application/json; charset=utf-8\\\\r\\\\n\\\",\\\"body\\\":\\\"{\\\\\\\"ResponseMetadata\\\\\\\":{\\\\\\\"RequestId\\\\\\\":\\\\\\\"20260309200708AB237F5DD9F049355D21\\\\\\\",\\\\\\\"Action\\\\\\\":\\\\\\\"ApplyImageUpload\\\\\\\",\\\\\\\"Version\\\\\\\":\\\\\\\"2018-08-01\\\\\\\",\\\\\\\"Service\\\\\\\":\\\\\\\"imagex\\\\\\\",\\\\\\\"Region\\\\\\\":\\\\\\\"cn-north-1\\\\\\\"},\\\\\\\"Result\\\\\\\":{\\\\\\\"UploadAddress\\\\\\\":{\\\\\\\"StoreInfos\\\\\\\":[{\\\\\\\"StoreUri\\\\\\\":\\\\\\\"tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\\\\\",\\\\\\\"Auth\\\\\\\":\\\\\\\"SpaceKey/jm8ajry58r/1/:version:v2:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NzMwNzk2MjksInNpZ25hdHVyZUluZm8iOnsiYWNjZXNzS2V5IjoiZmFrZV9hY2Nlc3Nfa2V5IiwiYnVja2V0IjoidG9zLWNuLWktam04YWpyeTU4ciIsImV4cGlyZSI6MTc3MzA3OTYyOSwiZmlsZUluZm9zIjpbeyJvaWRLZXkiOiI4Y2E2ZGYyMWFkNGI0OTBhOTFkYWExOTU3MTc4MjFlNiIsImZpbGVUeXBlIjoiMSJ9XSwiZXh0cmEiOnsiYWNjb3VudF9wcm9kdWN0IjoiaW1hZ2V4IiwiYmxvY2tfbW9kZSI6IiIsImNvbnRlbnRfdHlwZV9ibG9jayI6IntcIm1pbWVfcGN0XCI6MCxcIm1vZGVcIjowLFwibWltZV9saXN0XCI6bnVsbCxcImNvbmZsaWN0X2Jsb2NrXCI6ZmFsc2V9IiwiZW5jcnlwdF9hbGdvIjoiIiwiZW5jcnlwdF9rZXkiOiIiLCJzcGFjZSI6ImptOGFqcnk1OHIiLCJ0b3NfbWV0YSI6IntcIlVTRVJfSURcIjpcIjk1NTE5MTk0ODk3XCJ9In19fQ.4q_V3bnyUtq1ASIilAe5i4KQAL8JaclJqYcop6okOsc\\\\\\\",\\\\\\\"UploadID\\\\\\\":\\\\\\\"ddd130a223454183b2f722fede1b6b37\\\\\\\"}],\\\\\\\"UploadHosts\\\\\\\":[\\\\\\\"tos-lf-x.snssdk.com\\\\\\\"],\\\\\\\"UploadHeader\\\\\\\":null,\\\\\\\"SessionKey\\\\\\\":\\\\\\\"eyJhY2NvdW50VHlwZSI6IkltYWdlWCIsImFwcElkIjoiIiwiYml6VHlwZSI6IiIsImZpbGVUeXBlIjoiaW1hZ2UiLCJsZWdhbCI6IiIsInN0b3JlSW5mb3MiOiJbe1wiU3RvcmVVcmlcIjpcInRvcy1jbi1pLWptOGFqcnk1OHIvOGNhNmRmMjFhZDRiNDkwYTkxZGFhMTk1NzE3ODIxZTZcIixcIkF1dGhcIjpcIlNwYWNlS2V5L2ptOGFqcnk1OHIvMS86dmVyc2lvbjp2MjpleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKbGVIQWlPakUzTnpNd056azJNamtzSW5OcFoyNWhkSFZ5WlVsdVptOGlPbnNpWVdOalpYTnpTMlY1SWpvaVptRnJaVjloWTJObGMzTmZhMlY1SWl3aVluVmphMlYwSWpvaWRHOXpMV051TFdrdGFtMDRZV3B5ZVRVNGNpSXNJbVY0Y0dseVpTSTZNVGMzTXpBM09UWXlPU3dpWm1sc1pVbHVabTl6SWpwYmV5SnZhV1JMWlhraU9pSTRZMkUyWkdZeU1XRmtOR0kwT1RCaE9URmtZV0V4T1RVM01UYzRNakZsTmlJc0ltWnBiR1ZVZVhCbElqb2lNU0o5WFN3aVpYaDBjbUVpT25zaVlXTmpiM1Z1ZEY5d2NtOWtkV04wSWpvaWFXMWhaMlY0SWl3aVlteHZZMnRmYlc5a1pTSTZJaUlzSW1OdmJuUmxiblJmZEhsd1pWOWliRzlqYXlJNkludGNJbTFwYldWZmNHTjBYQ0k2TUN4Y0ltMXZaR1ZjSWpvd0xGd2liV2x0WlY5c2FYTjBYQ0k2Ym5Wc2JDeGNJbU52Ym1ac2FXTjBYMkpzYjJOclhDSTZabUZzYzJWOUlpd2laVzVqY25sd2RGOWhiR2R2SWpvaUlpd2laVzVqY25sd2RGOXJaWGtpT2lJaUxDSnpjR0ZqWlNJNkltcHRPR0ZxY25rMU9ISWlMQ0owYjNOZmJXVjBZU0k2SW50Y0lsVlRSVkpmU1VSY0lqcGNJamsxTlRFNU1UazBPRGszWENKOUluMTlmUS40cV9WM2JueVV0cTFBU0lpbEFlNWk0S1FBTDhKYWNsSnFZY29wNm9rT3NjXCIsXCJVcGxvYWRJRFwiOlwiZGRkMTMwYTIyMzQ1NDE4M2IyZjcyMmZlZGUxYjZiMzdcIixcIlVwbG9hZEhlYWRlclwiOm51bGwsXCJTdG9yYWdlSGVhZGVyXCI6bnVsbH1dIiwidXBsb2FkSG9zdCI6InRvcy1sZi14LnNuc3Nkay5jb20iLCJ1cmkiOiJ0b3MtY24taS1qbThhanJ5NThyLzhjYTZkZjIxYWQ0YjQ5MGE5MWRhYTE5NTcxNzgyMWU2IiwidXNlcklkIjoiIn0=\\\\\\\",\\\\\\\"Cloud\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"CloudAuth\\\\\\\":\\\\\\\"\\\\\\\"},\\\\\\\"FallbackUploadAddress\\\\\\\":{\\\\\\\"StoreInfos\\\\\\\":null,\\\\\\\"UploadHosts\\\\\\\":null,\\\\\\\"UploadHeader\\\\\\\":null,\\\\\\\"SessionKey\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"Cloud\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"CloudAuth\\\\\\\":\\\\\\\"\\\\\\\"},\\\\\\\"InnerUploadAddress\\\\\\\":{\\\\\\\"UploadNodes\\\\\\\":[{\\\\\\\"StoreInfos\\\\\\\":[{\\\\\\\"StoreUri\\\\\\\":\\\\\\\"tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\\\\\",\\\\\\\"Auth\\\\\\\":\\\\\\\"SpaceKey/jm8ajry58r/1/:version:v2:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NzMwNzk2MjksInNpZ25hdHVyZUluZm8iOnsiYWNjZXNzS2V5IjoiZmFrZV9hY2Nlc3Nfa2V5IiwiYnVja2V0IjoidG9zLWNuLWktam04YWpyeTU4ciIsImV4cGlyZSI6MTc3MzA3OTYyOSwiZmlsZUluZm9zIjpbeyJvaWRLZXkiOiI4Y2E2ZGYyMWFkNGI0OTBhOTFkYWExOTU3MTc4MjFlNiIsImZpbGVUeXBlIjoiMSJ9XSwiZXh0cmEiOnsiYWNjb3VudF9wcm9kdWN0IjoiaW1hZ2V4IiwiYmxvY2tfbW9kZSI6IiIsImNvbnRlbnRfdHlwZV9ibG9jayI6IntcIm1pbWVfcGN0XCI6MCxcIm1vZGVcIjowLFwibWltZV9saXN0XCI6bnVsbCxcImNvbmZsaWN0X2Jsb2NrXCI6ZmFsc2V9IiwiZW5jcnlwdF9hbGdvIjoiIiwiZW5jcnlwdF9rZXkiOiIiLCJzcGFjZSI6ImptOGFqcnk1OHIiLCJ0b3NfbWV0YSI6IntcIlVTRVJfSURcIjpcIjk1NTE5MTk0ODk3XCJ9In19fQ.4q_V3bnyUtq1ASIilAe5i4KQAL8JaclJqYcop6okOsc\\\\\\\",\\\\\\\"UploadID\\\\\\\":\\\\\\\"ddd130a223454183b2f722fe", + "body_length": 10016 + }, + { + "url": "https://imagex.bytedanceapi.com/?Action=CommitImageUpload&Version=2018-08-01&ServiceId=jm8ajry58r&app_id=2906&user_id=95519194897", + "method": "POST", + "content_type": "application/json", + "body": "{\"SessionKey\":\"eyJhY2NvdW50VHlwZSI6IkltYWdlWCIsImFwcElkIjoiIiwiYml6VHlwZSI6IiIsImZpbGVUeXBlIjoiaW1hZ2UiLCJsZWdhbCI6IiIsInN0b3JlSW5mb3MiOiJbe1wiU3RvcmVVcmlcIjpcInRvcy1jbi1pLWptOGFqcnk1OHIvOGNhNmRmMjFhZDRiNDkwYTkxZGFhMTk1NzE3ODIxZTZcIixcIkF1dGhcIjpcIlNwYWNlS2V5L2ptOGFqcnk1OHIvMS86dmVyc2lvbjp2MjpleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKbGVIQWlPakUzTnpNd056azJNamtzSW5OcFoyNWhkSFZ5WlVsdVptOGlPbnNpWVdOalpYTnpTMlY1SWpvaVptRnJaVjloWTJObGMzTmZhMlY1SWl3aVluVmphMlYwSWpvaWRHOXpMV051TFdrdGFtMDRZV3B5ZVRVNGNpSXNJbVY0Y0dseVpTSTZNVGMzTXpBM09UWXlPU3dpWm1sc1pVbHVabTl6SWpwYmV5SnZhV1JMWlhraU9pSTRZMkUyWkdZeU1XRmtOR0kwT1RCaE9URmtZV0V4T1RVM01UYzRNakZsTmlJc0ltWnBiR1ZVZVhCbElqb2lNU0o5WFN3aVpYaDBjbUVpT25zaVlXTmpiM1Z1ZEY5d2NtOWtkV04wSWpvaWFXMWhaMlY0SWl3aVlteHZZMnRmYlc5a1pTSTZJaUlzSW1OdmJuUmxiblJmZEhsd1pWOWliRzlqYXlJNkludGNJbTFwYldWZmNHTjBYQ0k2TUN4Y0ltMXZaR1ZjSWpvd0xGd2liV2x0WlY5c2FYTjBYQ0k2Ym5Wc2JDeGNJbU52Ym1ac2FXTjBYMkpzYjJOclhDSTZabUZzYzJWOUlpd2laVzVqY25sd2RGOWhiR2R2SWpvaUlpd2laVzVqY25sd2RGOXJaWGtpT2lJaUxDSnpjR0ZqWlNJNkltcHRPR0ZxY25rMU9ISWlMQ0owYjNOZmJXVjBZU0k2SW50Y0lsVlRSVkpmU1VSY0lqcGNJamsxTlRFNU1UazBPRGszWENKOUluMTlmUS40cV9WM2JueVV0cTFBU0lpbEFlNWk0S1FBTDhKYWNsSnFZY29wNm9rT3NjXCIsXCJVcGxvYWRJRFwiOlwiZGRkMTMwYTIyMzQ1NDE4M2IyZjcyMmZlZGUxYjZiMzdcIixcIlVwbG9hZEhlYWRlclwiOm51bGwsXCJTdG9yYWdlSGVhZGVyXCI6bnVsbH1dIiwidXBsb2FkSG9zdCI6InRvcy1sZi14LnNuc3Nkay5jb20iLCJ1cmkiOiJ0b3MtY24taS1qbThhanJ5NThyLzhjYTZkZjIxYWQ0YjQ5MGE5MWRhYTE5NTcxNzgyMWU2IiwidXNlcklkIjoiIn0=\"}", + "body_length": 1465 + }, + { + "url": "https://mcs.zijieapi.com/list?aid=2562&sdk_version=5.3.7&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "[{\"events\":[{\"event\":\"image_999\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":999,\\\"fk\\\":\\\"file_1773058028649_776752\\\",\\\"fs\\\":134675,\\\"ts\\\":1773058029,\\\"sst\\\":1773058028650,\\\"set\\\":1773058029003,\\\"dur\\\":361,\\\"tdur\\\":714,\\\"durs\\\":0.361,\\\"tdurs\\\":0.714,\\\"ucsrr\\\":false,\\\"skipCommit\\\":0,\\\"experimental\\\":0,\\\"type\\\":\\\"taskStart\\\",\\\"oid\\\":\\\"tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\",\\\"upload_host\\\":\\\"https://tos-lf-x.snssdk.com\\\",\\\"ex\\\":\\\"{\\\\\\\"msg\\\\\\\":\\\\\\\"taskStart 5 complete\\\\\\\"}\\\",\\\"event_index\\\":1773058062597}\",\"local_time_ms\":1773058029364,\"is_bav\":0,\"session_id\":\"ae2bd8e2-b70e-4b38-a882-f0eb4090e7c1\"},{\"event\":\"image_1003\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":1003,\\\"fk\\\":\\\"file_1773058028649_776752\\\",\\\"fs\\\":134675,\\\"req\\\":{\\\"url\\\":\\\"https://tos-lf-x.snssdk.com/upload/v1/tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\",\\\"param\\\":{}},\\\"res\\\":{\\\"status\\\":200,\\\"header\\\":\\\"cache-control: max-age=0, no-cache, no-store, must-revalidate, proxy-revalidate\\\\r\\\\ncontent-length: 79\\\\r\\\\ncontent-type: application/json; charset=utf-8\\\\r\\\\nexpires: 0\\\\r\\\\npragma: no-cache\\\\r\\\\n\\\",\\\"body\\\":\\\"{\\\\\\\"code\\\\\\\":2000,\\\\\\\"apiversion\\\\\\\":\\\\\\\"v1\\\\\\\",\\\\\\\"message\\\\\\\":\\\\\\\"Success\\\\\\\",\\\\\\\"data\\\\\\\":{\\\\\\\"crc32\\\\\\\":\\\\\\\"539061eb\\\\\\\"}}\\\"},\\\"ts\\\":1773058029,\\\"sst\\\":1773058029003,\\\"set\\\":1773058029364,\\\"dur\\\":361,\\\"tdur\\\":714,\\\"durs\\\":0.361,\\\"tdurs\\\":0.714,\\\"ucsrr\\\":false,\\\"skipCommit\\\":0,\\\"experimental\\\":0,\\\"type\\\":\\\"success\\\",\\\"oid\\\":\\\"tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\",\\\"upload_host\\\":\\\"https://tos-lf-x.snssdk.com\\\",\\\"ex\\\":\\\"{\\\\\\\"msg\\\\\\\":\\\\\\\"direct upload success\\\\\\\"}\\\",\\\"event_index\\\":1773058062596}\",\"local_time_ms\":1773058029364,\"is_bav\":0,\"session_id\":\"ae2bd8e2-b70e-4b38-a882-f0eb4090e7c1\"}],\"user\":{\"user_unique_id\":\"95519194897\",\"web_id\":\"7615225443776448000\"},\"header\":{\"app_id\":2562,\"os_name\":\"mac\",\"os_version\":\"10_15_7\",\"device_model\":\"Macintosh\",\"language\":\"zh-CN\",\"platform\":\"web\",\"sdk_version\":\"5.3.7\",\"sdk_lib\":\"js\",\"timezone\":8,\"tz_offset\":-28800,\"resolution\":\"1280x720\",\"browser\":\"Chrome\",\"browser_version\":\"143.0.0.0\",\"referrer\":\"\",\"referrer_host\":\"\",\"width\":1280,\"height\":720,\"screen_width\":1280,\"screen_height\":720,\"tracer_data\":\"{\\\"$utm_from_url\\\":1}\",\"custom\":\"{\\\"network_type\\\":\\\"4g\\\"}\"},\"local_time\":1773058029,\"verbose\":1}]", + "body_length": 2409 + }, + { + "url": "https://mcs.zijieapi.com/list?aid=2562&sdk_version=5.3.7&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "[{\"events\":[{\"event\":\"image_1005\",\"params\":\"{\\\"sdk_version\\\":\\\"2.1.1-alpha.21\\\",\\\"line_app_id\\\":2906,\\\"log_type\\\":\\\"image_upload\\\",\\\"stage\\\":1005,\\\"fk\\\":\\\"file_1773058028649_776752\\\",\\\"fs\\\":134675,\\\"req\\\":{\\\"url\\\":\\\"https://imagex.bytedanceapi.com/?Action=CommitImageUpload&Version=2018-08-01&ServiceId=jm8ajry58r&app_id=2906&user_id=95519194897\\\",\\\"param\\\":\\\"{\\\\\\\"SessionKey\\\\\\\":\\\\\\\"eyJhY2NvdW50VHlwZSI6IkltYWdlWCIsImFwcElkIjoiIiwiYml6VHlwZSI6IiIsImZpbGVUeXBlIjoiaW1hZ2UiLCJsZWdhbCI6IiIsInN0b3JlSW5mb3MiOiJbe1wiU3RvcmVVcmlcIjpcInRvcy1jbi1pLWptOGFqcnk1OHIvOGNhNmRmMjFhZDRiNDkwYTkxZGFhMTk1NzE3ODIxZTZcIixcIkF1dGhcIjpcIlNwYWNlS2V5L2ptOGFqcnk1OHIvMS86dmVyc2lvbjp2MjpleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKbGVIQWlPakUzTnpNd056azJNamtzSW5OcFoyNWhkSFZ5WlVsdVptOGlPbnNpWVdOalpYTnpTMlY1SWpvaVptRnJaVjloWTJObGMzTmZhMlY1SWl3aVluVmphMlYwSWpvaWRHOXpMV051TFdrdGFtMDRZV3B5ZVRVNGNpSXNJbVY0Y0dseVpTSTZNVGMzTXpBM09UWXlPU3dpWm1sc1pVbHVabTl6SWpwYmV5SnZhV1JMWlhraU9pSTRZMkUyWkdZeU1XRmtOR0kwT1RCaE9URmtZV0V4T1RVM01UYzRNakZsTmlJc0ltWnBiR1ZVZVhCbElqb2lNU0o5WFN3aVpYaDBjbUVpT25zaVlXTmpiM1Z1ZEY5d2NtOWtkV04wSWpvaWFXMWhaMlY0SWl3aVlteHZZMnRmYlc5a1pTSTZJaUlzSW1OdmJuUmxiblJmZEhsd1pWOWliRzlqYXlJNkludGNJbTFwYldWZmNHTjBYQ0k2TUN4Y0ltMXZaR1ZjSWpvd0xGd2liV2x0WlY5c2FYTjBYQ0k2Ym5Wc2JDeGNJbU52Ym1ac2FXTjBYMkpzYjJOclhDSTZabUZzYzJWOUlpd2laVzVqY25sd2RGOWhiR2R2SWpvaUlpd2laVzVqY25sd2RGOXJaWGtpT2lJaUxDSnpjR0ZqWlNJNkltcHRPR0ZxY25rMU9ISWlMQ0owYjNOZmJXVjBZU0k2SW50Y0lsVlRSVkpmU1VSY0lqcGNJamsxTlRFNU1UazBPRGszWENKOUluMTlmUS40cV9WM2JueVV0cTFBU0lpbEFlNWk0S1FBTDhKYWNsSnFZY29wNm9rT3NjXCIsXCJVcGxvYWRJRFwiOlwiZGRkMTMwYTIyMzQ1NDE4M2IyZjcyMmZlZGUxYjZiMzdcIixcIlVwbG9hZEhlYWRlclwiOm51bGwsXCJTdG9yYWdlSGVhZGVyXCI6bnVsbH1dIiwidXBsb2FkSG9zdCI6InRvcy1sZi14LnNuc3Nkay5jb20iLCJ1cmkiOiJ0b3MtY24taS1qbThhanJ5NThyLzhjYTZkZjIxYWQ0YjQ5MGE5MWRhYTE5NTcxNzgyMWU2IiwidXNlcklkIjoiIn0=\\\\\\\"}\\\"},\\\"res\\\":{\\\"status\\\":200,\\\"header\\\":\\\"content-type: application/json; charset=utf-8\\\\r\\\\n\\\",\\\"body\\\":\\\"{\\\\\\\"ResponseMetadata\\\\\\\":{\\\\\\\"RequestId\\\\\\\":\\\\\\\"20260309200709D0960088F6B077D807B9\\\\\\\",\\\\\\\"Action\\\\\\\":\\\\\\\"CommitImageUpload\\\\\\\",\\\\\\\"Version\\\\\\\":\\\\\\\"2018-08-01\\\\\\\",\\\\\\\"Service\\\\\\\":\\\\\\\"imagex\\\\\\\",\\\\\\\"Region\\\\\\\":\\\\\\\"cn-north-1\\\\\\\"},\\\\\\\"Result\\\\\\\":{\\\\\\\"Results\\\\\\\":[{\\\\\\\"Uri\\\\\\\":\\\\\\\"tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\\\\\",\\\\\\\"UriStatus\\\\\\\":2000}],\\\\\\\"RequestId\\\\\\\":\\\\\\\"20260309200709D0960088F6B077D807B9\\\\\\\",\\\\\\\"PluginResult\\\\\\\":[{\\\\\\\"FileName\\\\\\\":\\\\\\\"8ca6df21ad4b490a91daa195717821e6\\\\\\\",\\\\\\\"SourceUri\\\\\\\":\\\\\\\"tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\\\\\",\\\\\\\"ImageUri\\\\\\\":\\\\\\\"tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\\\\\",\\\\\\\"ImageWidth\\\\\\\":810,\\\\\\\"ImageHeight\\\\\\\":1080,\\\\\\\"ImageMd5\\\\\\\":\\\\\\\"3e83549da7a9d471bc88660eb756cbfb\\\\\\\",\\\\\\\"ImageFormat\\\\\\\":\\\\\\\"jpeg\\\\\\\",\\\\\\\"ImageSize\\\\\\\":134675,\\\\\\\"FrameCnt\\\\\\\":1,\\\\\\\"ContentType\\\\\\\":\\\\\\\"application/json; charset=utf-8\\\\\\\",\\\\\\\"NotFound\\\\\\\":false,\\\\\\\"Orientation\\\\\\\":{\\\\\\\"Rotation\\\\\\\":0,\\\\\\\"Fliph\\\\\\\":false,\\\\\\\"Flipv\\\\\\\":false},\\\\\\\"ColorModel\\\\\\\":\\\\\\\"ycbcr\\\\\\\",\\\\\\\"ImageAve\\\\\\\":\\\\\\\"\\\\\\\"}]}}\\\"},\\\"ts\\\":1773058030,\\\"sst\\\":1773058029364,\\\"set\\\":1773058029713,\\\"dur\\\":349,\\\"tdur\\\":1063,\\\"durs\\\":0.349,\\\"tdurs\\\":1.063,\\\"ucsrr\\\":false,\\\"skipCommit\\\":0,\\\"experimental\\\":0,\\\"type\\\":\\\"success\\\",\\\"oid\\\":\\\"tos-cn-i-jm8ajry58r/8ca6df21ad4b490a91daa195717821e6\\\",\\\"upload_host\\\":\\\"https://tos-lf-x.snssdk.com\\\",\\\"speed\\\":124,\\\"ex\\\":\\\"{\\\\\\\"msg\\\\\\\":\\\\\\\"commit upload successful\\\\\\\"}\\\",\\\"event_index\\\":1773058062598}\",\"local_time_ms\":1773058029713,\"is_bav\":0,\"session_id\":\"ae2bd8e2-b70e-4b38-a882-f0eb4090e7c1\"}],\"user\":{\"user_unique_id\":\"95519194897\",\"web_id\":\"7615225443776448000\"},\"header\":{\"app_id\":2562,\"os_name\":\"mac\",\"os_version\":\"10_15_7\",\"device_model\":\"Macintosh\",\"language\":\"zh-CN\",\"platform\":\"web\",\"sdk_version\":\"5.3.7\",\"sdk_lib\":\"js\",\"timezone\":8,\"tz_offset\":-28800,\"resolution\":\"1280x720\",\"browser\":\"Chrome\",\"browser_version\":\"143.0.0.0\",\"referrer\":\"\",\"referrer_host\":\"\",\"width\":1280,\"height\":720,\"screen_width\":1280,\"screen_height\":720,\"tracer_data\":\"{\\\"$utm_from_url\\\":1}\",\"custom\":\"{\\\"network_type\\\":\\\"4g\\\"}\"},\"local_time\":1773058029,\"verbose\":1}]", + "body_length": 4072 + }, + { + "url": "https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll?msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=dj0Vhtt7mqRcP3MtYOmct3qlfLglNT8yEGT%2FWaJjeOi1ahtGNpeKTx8UnOzBc1SdQ8B9k9dHRdY%2FGnEaY4Dv01Vpzmpfuz4WYUVcnh8o0qwVPMksDqmNC0sFFwsYMbJLeQ9Ui1vRIsMygdOlIHKpAx-ay5zjRYmpbNHcdFz9SEWgDCukix-iO9DpTyJuUtAS", + "method": "POST", + "content_type": "application/json", + "body": "{\"resource_list\":[{\"type\":2,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"duration\":66.366,\"cover_uri\":\"\"}],\"source\":1,\"is_redetect\":false,\"task_id\":\"7615226012955204398\"}", + "body_length": 172 + }, + { + "url": "https://security.zijieapi.com/api/metrics/emit", + "method": "POST", + "content_type": "application/json", + "body": "{\"values\":[{\"name\":\"decision.latency\",\"tags\":{\"decisionName\":\"XHR_REQUEST_SEND\",\"projectId\":\"40\",\"env\":\"online\",\"sdkVersion\":\"1.0.29\",\"name\":\"-\",\"strategyName\":\"-\",\"strategyVersion\":\"-\",\"functionName\":\"-\",\"actionType\":\"-\"},\"value\":1,\"type\":\"time\"},{\"name\":\"decision.latency\",\"tags\":{\"decisionName\":\"-\",\"projectId\":\"40\",\"env\":\"online\",\"sdkVersion\":\"1.0.29\",\"name\":\"-\",\"strategyName\":\"-\",\"strategyVersion\":\"-\",\"functionName\":\"-\",\"actionType\":\"-\"},\"value\":2,\"type\":\"time\"},{\"name\":\"decision.latency\",\"tags\":{\"decisionName\":\"-\",\"projectId\":\"40\",\"env\":\"online\",\"sdkVersion\":\"1.0.29\",\"name\":\"-\",\"strategyName\":\"-\",\"strategyVersion\":\"-\",\"functionName\":\"-\",\"actionType\":\"-\"},\"value\":3,\"type\":\"time\"}]}", + "body_length": 693 + }, + { + "url": "https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll?msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=mXsVhzUyxN85CpKtYODCtUZlDzglNTuyETTOWnJjCON2aZzG4pe%2F0OmUrOFQrXuDK8pVkHp7RdOlGfDaYUDt0I3pzmkkSP4WuUVVn80L0qwVaMvsDNDZCh0FuwsYlbvLe59ti1f5lsMwgdclVroMAP-at5zERObpSHHIdFYyCEAhDAukin-sOHgdOgGcUfdf", + "method": "POST", + "content_type": "application/json", + "body": "{\"resource_list\":[{\"type\":2,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"duration\":66.366,\"cover_uri\":\"\"}],\"source\":1,\"is_redetect\":false,\"task_id\":\"7615226012955204398\"}", + "body_length": 172 + }, + { + "url": "https://creator.douyin.com/passport/web/send_code/?passport_jssdk_version=5.1.2&passport_jssdk_type=lite&is_from_ttaccountsdk=1&aid=2906&language=zh&account_app_language=zh-CN&new_authn_sdk_version=1.0.0.379-web&is_vcd=1&biz_trace_id=d6264560&msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=mfsfD7ULQd%2FRcpMGmOmAtX5lTFglrTSyLzTxWwTjt59DahUYSNeO0uXUjPF5rXuD%2FmBAk9QH5dz3Txfb8Us52A3kLmkDSkGSY4Vcn0Xo8qNVPzksDNDpC00FuwBGMRkLl%2F9GiIv56s0ygdOlIr", + "method": "POST", + "content_type": "application/x-www-form-urlencoded", + "body": "mix_mode=1&type=3737&encrypt_uid=tJSy%2F4Y8PxBD283G%2F235dDBXxcB8inhN40k5Urz0lnlfpGeOSUFGmf2Rd8Q%3D&verify_ticket=©writing_key=creator&new_verify_flow=&is6Digits=1&aid=2906&new_authn_sdk_version=1.0.0.379-web", + "body_length": 212 + }, + { + "url": "https://mcs.zijieapi.com/list?aid=1661&sdk_version=5.3.8&device_platform=web", + "method": "POST", + "content_type": "application/json; charset=UTF-8", + "body": "[{\"events\":[{\"event\":\"web_bd_ticket_dtrait_response\",\"params\":\"{\\\"_staging_flag\\\":0,\\\"sdk_version\\\":\\\"3.3.11\\\",\\\"self_platform\\\":\\\"web\\\",\\\"params_for_special\\\":\\\"uc_login\\\",\\\"hostname\\\":\\\"creator.douyin.com\\\",\\\"centralVersion\\\":\\\"d0\\\",\\\"dTraitVersion\\\":\\\"0\\\",\\\"edgeVersion\\\":\\\"d0\\\",\\\"urlVersion\\\":\\\"1.0.0.16\\\",\\\"dTraitContainerSdkVersion\\\":\\\"1.1.0\\\",\\\"useBuildIn\\\":1,\\\"duration\\\":270,\\\"pathname\\\":\\\"/passport/web/send_code/\\\",\\\"host\\\":\\\"creator.douyin.com\\\",\\\"has_dTraitToken\\\":0,\\\"logid\\\":\\\"20260309200712264CA7FCEB8223C4B0D3\\\",\\\"http_code\\\":200,\\\"performance_time\\\":38735.5,\\\"event_index\\\":1773058761595}\",\"local_time_ms\":1773058032986,\"is_bav\":0,\"session_id\":\"e29be62b-8fef-40a3-8f60-b191b08b6d00\"}],\"user\":{\"user_unique_id\":\"95519194897\",\"user_id\":\"95519194897\",\"device_id\":\"95519194897\",\"web_id\":\"1515143131341514113\"},\"header\":{\"app_id\":1661,\"os_name\":\"mac\",\"os_version\":\"10_15_7\",\"device_model\":\"Macintosh\",\"language\":\"zh-CN\",\"platform\":\"web\",\"sdk_version\":\"5.3.8\",\"sdk_lib\":\"js\",\"timezone\":8,\"tz_offset\":-28800,\"resolution\":\"1280x720\",\"browser\":\"Chrome\",\"browser_version\":\"143.0.0.0\",\"referrer\":\"\",\"referrer_host\":\"\",\"width\":1280,\"height\":720,\"screen_width\":1280,\"screen_height\":720,\"tracer_data\":\"{\\\"$utm_from_url\\\":1}\",\"custom\":\"{\\\"network_type\\\":\\\"4g\\\"}\"},\"local_time\":1773058033,\"verbose\":1}]", + "body_length": 1304 + }, + { + "url": "https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll?msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=O7sVkqtLxo8Vc3KbmKD9tWqUyeEArBWyEGixWajjeONZaZtGl3Hd0a0UjOFB7HSDKmB9kCQ7VdKATdjGmUm70lIkqmkDug7jYs5Cn8fL%2FqN3PMJsENDMC0sFLwsGlbvLaA97iIX5UsMLgDclnroNAx5Ge5FimQYpSHHIdoTyHEW6fSWkhn-hOeDDPyTq6j%3D%3D", + "method": "POST", + "content_type": "application/json", + "body": "{\"resource_list\":[{\"type\":2,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"duration\":66.366,\"cover_uri\":\"\"}],\"source\":1,\"is_redetect\":false,\"task_id\":\"7615226012955204398\"}", + "body_length": 172 + }, + { + "url": "https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll?msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=Ofs5gFyLEp%2FfapFt8CmVt6ZUHuDArPSyLzToWr7jHONZPZzGG3H%2Fsr0UJOzBqXudQ8BAkHQ7fdY%2FTVDG84m70I1pqmpvS24Wu4VIn0vL8qNVaFvsErmqC00zzwBGM5kLa%2F9Ji16RlsMwgd5lVrKqANlG75zimmmpWrH9dKT9yEWgfC8kin3wOeD2NgGqkf%3D%3D", + "method": "POST", + "content_type": "application/json", + "body": "{\"resource_list\":[{\"type\":2,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"duration\":66.366,\"cover_uri\":\"\"}],\"source\":1,\"is_redetect\":false,\"task_id\":\"7615226012955204398\"}", + "body_length": 172 + }, + { + "url": "https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll?msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=xJ4RkwWjD2QRcp%2FtmODCt6NUYSDMNPWyEtTQWr7n7ONIawzG-pedsn8lcOz5YXtD%2FRBAkHQ74dYMGjDGYsmJ011kLmpkSkXRus5VngsLZqq3aMvsgNmFC80FKwBYlbvL-QCJi1vRUsM7gDclnroZAN-a75zERYRpWqH9doY99EW6fSWkwo3iOCDpT6Na0CKt", + "method": "POST", + "content_type": "application/json", + "body": "{\"resource_list\":[{\"type\":2,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"duration\":66.366,\"cover_uri\":\"\"}],\"source\":1,\"is_redetect\":false,\"task_id\":\"7615226012955204398\"}", + "body_length": 172 + }, + { + "url": "https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll?msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=EJs5kqUiD2mVP3FSYcDCtAdlXej%2FrPSyDzTOWajn7OiZaqtGJ1ed0r0UnOFQu1zd%2F8BnkHQ7vdYAGnjGm0Dt0A3pumkkSMXS80VcnhmohqqpPMvsgNmFC0mFowBGl5kLeA9XiA65UsM76x5lIHKpAOlaH5FERYmpSrHCdKY99EWgfWSkin-sOHkpEgJt03cv", + "method": "POST", + "content_type": "application/json", + "body": "{\"resource_list\":[{\"type\":2,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"duration\":66.366,\"cover_uri\":\"\"}],\"source\":1,\"is_redetect\":false,\"task_id\":\"7615226012955204398\"}", + "body_length": 172 + }, + { + "url": "https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll?msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=Qy4RkwWydxQfPpMtucD5tIqlUtVMNP8yEtTxWnJn9OiIah0Grpe%2F0csljOFQJwzdQ8B9kenH4dKAOdfa8sD40AIkLmpDu8w6m05In0so2qqpa0isEHmPCg8FwwBbM5JL-%2F9Gilv5WsMy6DOlIHoMAOlaH5FERmbpSHeVdqTy7EAhfWSkwx3zOHDkxgkt05Ov", + "method": "POST", + "content_type": "application/json", + "body": "{\"resource_list\":[{\"type\":2,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"duration\":66.366,\"cover_uri\":\"\"}],\"source\":1,\"is_redetect\":false,\"task_id\":\"7615226012955204398\"}", + "body_length": 172 + }, + { + "url": "https://creator.douyin.com/aweme/v1/post_assistant/fast_detect/poll?msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_1U-OMQ%3D%3D&a_bogus=QJ0Vgt7yDqWVep%2FtYOmntl2l2WnMrPWyDzidWaJn9ON2Ph0GX1Hdsn0UGOzQw10D%2FmBnk9nHVdYMOfjaY0mt0A1kwmpDSO0f8UVInhsLMqwdP0vsDrDZChsFowsGl5iLlACtil6RXs0L6dOlVNoZAOlGy5zEROmpbqecdozy7EAXfA8kkn3zOCDdYgaFUGx4", + "method": "POST", + "content_type": "application/json", + "body": "{\"resource_list\":[{\"type\":2,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"duration\":66.366,\"cover_uri\":\"\"}],\"source\":1,\"is_redetect\":false,\"task_id\":\"7615226012955204398\"}", + "body_length": 172 + } +] \ No newline at end of file diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/batch_publish_119.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/batch_publish_119.py new file mode 100644 index 00000000..798d3fee --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/batch_publish_119.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +119 场成片批量发布到抖音。 +- 读取成片目录与发布清单,逐条调用 douyin_publish.py。 +- 若未配置 tokens.json,只打印发布清单并提示先 OAuth 登录。 +与「抖音发布」Skill 配套;成片目录见 119场_抖音发布清单.md。 +""" + +import json +import subprocess +import sys +from pathlib import Path + +# 119 场成片目录(可按需改为环境变量或参数) +DEFAULT_Chengpian = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片") + +# 视频文件名 -> 发布标题(与 119场_抖音发布清单.md 一致) +TITLES = { + "早起不是为了开派对,是不吵老婆睡觉.mp4": "早起不是为了开派对,是不吵老婆睡觉。初衷就这一个。#Soul派对 #创业日记 #晨间直播 #私域干货", + "懒人的活法 动作简单有利可图正反馈.mp4": "懒有懒的活法:动作简单、有利可图、正反馈,就能坐得住。#Soul派对 #副业 #私域 #切片变现", +} + + +def main(): + script_dir = Path(__file__).resolve().parent + token_file = script_dir / "tokens.json" + if not token_file.exists(): + print("未配置 tokens.json,无法调用抖音开放平台 API。") + print("请先完成抖音 OAuth 登录,将 access_token、open_id 写入:") + print(f" {token_file}") + print("参见:参考资料/抖音开放平台_登录与发布流程.md") + print("\n本批次发布清单(可复制到抖音或卡罗维亚等工具):") + for fname, title in TITLES.items(): + p = DEFAULT_Chengpian / fname + print(f" - {fname}") + print(f" 标题: {title[:60]}...") + return 1 + + chengpian = DEFAULT_Chengpian + if not chengpian.exists(): + print(f"成片目录不存在: {chengpian}", file=sys.stderr) + return 1 + + publish_py = script_dir / "douyin_publish.py" + if not publish_py.exists(): + print(f"未找到 douyin_publish.py: {publish_py}", file=sys.stderr) + return 1 + + ok, fail = 0, 0 + for fname, title in TITLES.items(): + video_path = chengpian / fname + if not video_path.exists(): + print(f"跳过(文件不存在): {fname}") + fail += 1 + continue + cmd = [ + sys.executable, + str(publish_py), + "--video", str(video_path), + "--title", title, + "--token-file", str(token_file), + ] + ret = subprocess.run(cmd) + if ret.returncode == 0: + ok += 1 + else: + fail += 1 + + print(f"\n发布完成: 成功 {ok},失败 {fail}") + return 0 if fail == 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/create_v2_captured.json b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/create_v2_captured.json new file mode 100644 index 00000000..17246d10 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/create_v2_captured.json @@ -0,0 +1,26 @@ +[ + { + "url": "https://creator.douyin.com/web/api/media/aweme/create_v2/?read_aid=2906&cookie_enabled=true&screen_width=1280&screen_height=720&browser_language=zh-CN&browser_platform=MacIntel&browser_name=Mozilla&browser_version=5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_15_7%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F143.0.0.0+Safari%2F537.36&browser_online=true&timezone_name=Asia%2FShanghai&aid=1128&support_h265=1&msToken=lwnPlXVwrVICuG48jgVvkmKR4ed9tjO03P02sph3O8lMOFxixc-0graLHQ_Wlb8_NiRhDPIgOVwLdza5Dao1qoK4HjrUw8Q5dmu6qOBXeXlGsKyskhljpHFqdZ80SNMIT_9lxI73qqsYLQpE5zSDu4h5I9_z4vV4ZOW1tm3fmlgRkrm_", + "method": "POST", + "content_type": "application/json", + "body": "{\"item\":{\"common\":{\"text\":\"广点通能投Soul了 测试发布\",\"caption\":\"广点通能投Soul了 测试发布\",\"item_title\":\"\",\"activity\":\"[]\",\"text_extra\":\"[]\",\"challenges\":\"[]\",\"mentions\":\"[]\",\"hashtag_source\":\"\",\"hot_sentence\":\"\",\"interaction_stickers\":\"[]\",\"visibility_type\":0,\"download\":1,\"timing\":0,\"creation_id\":\"86o77cjp1773058000249\",\"media_type\":4,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"music_source\":0,\"music_id\":null},\"cover\":{\"custom_cover_image_height\":663,\"custom_cover_image_width\":497,\"poster\":\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\",\"poster_delay\":30.08,\"horizontal_custom_cover_image_uri\":\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\",\"horizontal_cover_tsp\":30.08,\"horizontal_custom_cover_image_height\":663,\"horizontal_custom_cover_image_width\":498,\"cover_tools_extend_info\":\"{\\\"recommendServerInfo\\\":{\\\"res\\\":[],\\\"times\\\":[]},\\\"recommendCoverList\\\":[{\\\"id\\\":\\\"7615586833bf667e27abd5345f67d8a4\\\",\\\"time\\\":20.08,\\\"uri\\\":\\\"NOT_READY\\\",\\\"frameIndex\\\":2,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/3d78919a-9166-480b-b901-aa8bb45afa65\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b094f7a6-6907-4843-ad73-c9b6956e39bf\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":true,\\\"uri1\\\":\\\"NOT_READY\\\"},{\\\"id\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"time\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"frameIndex\\\":3,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/a6eb4869-fb2b-4bc3-b20f-438783edd70d\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b15c59c4-deae-4ccd-a73f-86368d7f7b91\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\"},{\\\"id\\\":\\\"e8a806ba18960a6c0b925f25fdb65a03\\\",\\\"time\\\":40.08,\\\"uri\\\":\\\"NOT_READY\\\",\\\"frameIndex\\\":4,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/feb0d50b-a38a-49b0-acc4-0d0ff3b6153a\\\",\\\"cropHeight\\\":663.3667498826981,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.06012023985385895,1,0.6743487119674683],\\\"cropBox2\\\":[0,0.06012023985385895,1,0.6743487119674683],\\\"src\\\":\\\"blob:https://creator.douyin.com/079df6cf-5d25-4a22-bf2a-0b088b9ebb29\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"NOT_READY\\\"}],\\\"recommendCoverInfo\\\":{\\\"isFromRecommend\\\":true,\\\"isDefaultSelect\\\":false,\\\"isRecommendClickFrom\\\":\\\"outer\\\",\\\"selectInfo\\\":{\\\"id\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"time\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"frameIndex\\\":3,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/a6eb4869-fb2b-4bc3-b20f-438783edd70d\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b15c59c4-deae-4ccd-a73f-86368d7f7b91\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\"},\\\"editingInfo\\\":{},\\\"isStartEditing\\\":false},\\\"recommendCoverTime\\\":0,\\\"coverInfo\\\":{\\\"videoName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"coverEditLogInfo\\\":{\\\"is_cover_edit\\\":\\\"true\\\",\\\"cover_type\\\":\\\"recommend_pic\\\",\\\"is_setting_double_cover\\\":1,\\\"video_cover_source\\\":\\\"recommend_pic\\\",\\\"is_text_template\\\":0,\\\"is_text\\\":0,\\\"text_num\\\":0,\\\"is_cover_template\\\":0,\\\"tab_name\\\":\\\"\\\",\\\"sticker_tab_name\\\":\\\"\\\",\\\"filter_tab_name\\\":\\\"\\\",\\\"text_content\\\":\\\"\\\",\\\"text_template_content\\\":\\\"\\\",\\\"is_use_sticker\\\":0,\\\"is_use_filter\\\":0,\\\"text_template_id\\\":\\\"\\\",\\\"sticker_id\\\":\\\"\\\",\\\"filter_id\\\":[],\\\"second_cover_details\\\":\\\"{\\\\\\\"video_cover_source_landscape\\\\\\\":\\\\\\\"recommend_pic\\\\\\\",\\\\\\\"is_text_template_landscape\\\\\\\":0,\\\\\\\"is_text_landscape\\\\\\\":0,\\\\\\\"text_num_landscape\\\\\\\":0,\\\\\\\"is_cover_template_landscape\\\\\\\":0,\\\\\\\"tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_template_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"is_use_sticker_landscape\\\\\\\":0,\\\\\\\"is_use_filter_landscape\\\\\\\":0,\\\\\\\"text_template_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_id_landscape\\\\\\\":[]}\\\"},\\\"posterDelay\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"customCoverImageHeight\\\":663.3667176961899,\\\"customCoverImageWidth\\\":497.5250382721424,\\\"draft\\\":{\\\"slot\\\":\\\"tos://tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"width\\\":498,\\\"height\\\":663.3667176961899,\\\"templateId\\\":\\\"7293420016291151922\\\",\\\"templateUrl\\\":\\\"https://lf3-media.amemv.com/obj/media-platform-open/2e82e90e-d8d2-4664-9a22-eb21f304ef3a\\\"},\\\"edited\\\":true,\\\"type\\\":2,\\\"de", + "body_length": 13500, + "headers": { + "sec-ch-ua-platform": "\"macOS\"", + "x-tt-session-dtrait": "d0_FkGIwcjOBGVkrreJRhLMj2GAyT6Xh2dtRDWR7oUmwfguthLgGBzbrmWG6zu8RqxhLXdkq8roKxki0M6CjaDD33Q96YrILE+7XO4ddA6GMvaefrzzUwjl5TOJ9P6q1nL7VUa16mlQBB39mDL1Qvcru6dq+9bJ6oi9ufRx6xx/SPUG23zk1oUSIQDhP84nhgms/UC75zGM/hIZ6P1F7lnd3fKUE+MT4mhgU4Mb76ysns8krOix20WF26prHvrF7OU8lVuA09V3rNGTqStxPOVdia+NOpvWkKHrqKwm5bzJ0ch2tHo1EDPrfMO3lU3vysKid/OjwWcmPFlZg+cQSq2uhw==_LmJaprOUloKcVq69zGaoEgteH/tSqpVDFnMHLcj4s6LBKLZE4X7Y7463URLdwJ+D8RwJqo1opXIMXfGzI+fq0xXJVuQGVBPLlETq4PqlurlIXvhgx8XN1DBed6SRnkZJZaWNm+XxNcrBRwPUMy/FQmQi5pkBKtoxN6AabVXHr2fHkjYonylqFqcdzGU6sTKLqySWa0KbqKMY2WjRFb37MgtU3YGM3voLG6vGkNj7akeN2/bP5tyoavhTbmmWt5/Ij7u4jYIrj/oFOPcduxalNvWvVhiPnuK1irkH0DhYviFpsrq95c0bSR4Jhz72ocjgbfiMtsYIth0RDxtplaWIyqSYfVsDYayOOKmppVx3/Dnn/0n1wJ32TKOGAF4iXo/LsJTOwivnv+pXRX9nVmlywR9OlC2rnyCaGetYwab5GpvaT9z5fPYuQByX/z8bSNog8/eMLt+0Ta2wDM2PvX2w34D65aipjK430RUfxHtTV9U=", + "referer": "https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page", + "sec-ch-ua": "\"Chromium\";v=\"143\", \"Not A(Brand\";v=\"24\"", + "bd-ticket-guard-web-version": "2", + "sec-ch-ua-mobile": "?0", + "bd-ticket-guard-client-data": "eyJ0c19zaWduIjoidHMuMi4xNzVhNjY0NjYyYWZmNThhZmU3Y2I5MTgyMWIxOGM0MWEyNmFmNTU4ZWQwMzYxMjEyMGE0NmEyMjFlNzU1ZWUzYzRmYmU4N2QyMzE5Y2YwNTMxODYyNGNlZGExNDkxMWNhNDA2ZGVkYmViZWRkYjJlMzBmY2U4ZDRmYTAyNTc1ZCIsInJlcV9jb250ZW50IjoidGlja2V0LHBhdGgsdGltZXN0YW1wIiwicmVxX3NpZ24iOiJudzd3Zlh0bzYwbUc0Uk91OWZ5dEp0VElFZWxPS2VEamxCRVNrZWoxUm04PSIsInRpbWVzdGFtcCI6MTc3MzA1ODAyN30=", + "bd-ticket-guard-web-sign-type": "1", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36", + "accept": "application/json, text/plain, */*", + "x-secsdk-csrf-token": "000100000001a22775e255397d6311ea08cde8025d64f8a45c0f7df7f717e049fde934a1dde8189b2adaa85a6ba0", + "content-type": "application/json", + "bd-ticket-guard-ree-public-key": "BPNB/e+d134tog5uPa6LVRibIdxycNTFav5Bn8NyXatdz3EZGs3CsCyPD8bkGXTHK0mCs5PaqAyXkAXNEnIkwkw=", + "bd-ticket-guard-version": "2" + }, + "full_body": "{\"item\":{\"common\":{\"text\":\"广点通能投Soul了 测试发布\",\"caption\":\"广点通能投Soul了 测试发布\",\"item_title\":\"\",\"activity\":\"[]\",\"text_extra\":\"[]\",\"challenges\":\"[]\",\"mentions\":\"[]\",\"hashtag_source\":\"\",\"hot_sentence\":\"\",\"interaction_stickers\":\"[]\",\"visibility_type\":0,\"download\":1,\"timing\":0,\"creation_id\":\"86o77cjp1773058000249\",\"media_type\":4,\"video_id\":\"v0200fg10000d6nbfknog65sq49b99gg\",\"music_source\":0,\"music_id\":null},\"cover\":{\"custom_cover_image_height\":663,\"custom_cover_image_width\":497,\"poster\":\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\",\"poster_delay\":30.08,\"horizontal_custom_cover_image_uri\":\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\",\"horizontal_cover_tsp\":30.08,\"horizontal_custom_cover_image_height\":663,\"horizontal_custom_cover_image_width\":498,\"cover_tools_extend_info\":\"{\\\"recommendServerInfo\\\":{\\\"res\\\":[],\\\"times\\\":[]},\\\"recommendCoverList\\\":[{\\\"id\\\":\\\"7615586833bf667e27abd5345f67d8a4\\\",\\\"time\\\":20.08,\\\"uri\\\":\\\"NOT_READY\\\",\\\"frameIndex\\\":2,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/3d78919a-9166-480b-b901-aa8bb45afa65\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b094f7a6-6907-4843-ad73-c9b6956e39bf\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":true,\\\"uri1\\\":\\\"NOT_READY\\\"},{\\\"id\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"time\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"frameIndex\\\":3,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/a6eb4869-fb2b-4bc3-b20f-438783edd70d\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b15c59c4-deae-4ccd-a73f-86368d7f7b91\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\"},{\\\"id\\\":\\\"e8a806ba18960a6c0b925f25fdb65a03\\\",\\\"time\\\":40.08,\\\"uri\\\":\\\"NOT_READY\\\",\\\"frameIndex\\\":4,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/feb0d50b-a38a-49b0-acc4-0d0ff3b6153a\\\",\\\"cropHeight\\\":663.3667498826981,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.06012023985385895,1,0.6743487119674683],\\\"cropBox2\\\":[0,0.06012023985385895,1,0.6743487119674683],\\\"src\\\":\\\"blob:https://creator.douyin.com/079df6cf-5d25-4a22-bf2a-0b088b9ebb29\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"NOT_READY\\\"}],\\\"recommendCoverInfo\\\":{\\\"isFromRecommend\\\":true,\\\"isDefaultSelect\\\":false,\\\"isRecommendClickFrom\\\":\\\"outer\\\",\\\"selectInfo\\\":{\\\"id\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"time\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"frameIndex\\\":3,\\\"previewBlobSrc\\\":\\\"blob:https://creator.douyin.com/a6eb4869-fb2b-4bc3-b20f-438783edd70d\\\",\\\"cropHeight\\\":663.3667176961899,\\\"cropWidth\\\":498,\\\"cropBox\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"cropBox2\\\":[0,0.10020039975643158,1,0.7144288420677185],\\\"src\\\":\\\"blob:https://creator.douyin.com/b15c59c4-deae-4ccd-a73f-86368d7f7b91\\\",\\\"rawBlob\\\":{},\\\"fileName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"isAIGen\\\":false,\\\"uri1\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\"},\\\"editingInfo\\\":{},\\\"isStartEditing\\\":false},\\\"recommendCoverTime\\\":0,\\\"coverInfo\\\":{\\\"videoName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"coverEditLogInfo\\\":{\\\"is_cover_edit\\\":\\\"true\\\",\\\"cover_type\\\":\\\"recommend_pic\\\",\\\"is_setting_double_cover\\\":1,\\\"video_cover_source\\\":\\\"recommend_pic\\\",\\\"is_text_template\\\":0,\\\"is_text\\\":0,\\\"text_num\\\":0,\\\"is_cover_template\\\":0,\\\"tab_name\\\":\\\"\\\",\\\"sticker_tab_name\\\":\\\"\\\",\\\"filter_tab_name\\\":\\\"\\\",\\\"text_content\\\":\\\"\\\",\\\"text_template_content\\\":\\\"\\\",\\\"is_use_sticker\\\":0,\\\"is_use_filter\\\":0,\\\"text_template_id\\\":\\\"\\\",\\\"sticker_id\\\":\\\"\\\",\\\"filter_id\\\":[],\\\"second_cover_details\\\":\\\"{\\\\\\\"video_cover_source_landscape\\\\\\\":\\\\\\\"recommend_pic\\\\\\\",\\\\\\\"is_text_template_landscape\\\\\\\":0,\\\\\\\"is_text_landscape\\\\\\\":0,\\\\\\\"text_num_landscape\\\\\\\":0,\\\\\\\"is_cover_template_landscape\\\\\\\":0,\\\\\\\"tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_template_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"is_use_sticker_landscape\\\\\\\":0,\\\\\\\"is_use_filter_landscape\\\\\\\":0,\\\\\\\"text_template_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_id_landscape\\\\\\\":[]}\\\"},\\\"posterDelay\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"customCoverImageHeight\\\":663.3667176961899,\\\"customCoverImageWidth\\\":497.5250382721424,\\\"draft\\\":{\\\"slot\\\":\\\"tos://tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"width\\\":498,\\\"height\\\":663.3667176961899,\\\"templateId\\\":\\\"7293420016291151922\\\",\\\"templateUrl\\\":\\\"https://lf3-media.amemv.com/obj/media-platform-open/2e82e90e-d8d2-4664-9a22-eb21f304ef3a\\\"},\\\"edited\\\":true,\\\"type\\\":2,\\\"defaultUri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"horizontalDefaultUri\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"cropedUri\\\":\\\"tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55\\\",\\\"aiGenCoverId\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"url\\\":\\\"https://p0-creator-media-private.douyin.com/tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55~tplv-jm8ajry58r-image.jpeg?policy=eyJ2bSI6MywidWlkIjoiOTU1MTkxOTQ4OTcifQ%3D%3D&rk3s=70809c85&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1773061612&x-orig-sign=dQgmvtx%2FB9Zn1ReTjTqTS%2FRo9mA%3D\\\"},\\\"coverUrl\\\":\\\"https://p0-creator-media-private.douyin.com/tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55~tplv-jm8ajry58r-image.jpeg?policy=eyJ2bSI6MywidWlkIjoiOTU1MTkxOTQ4OTcifQ%3D%3D&rk3s=70809c85&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1773061612&x-orig-sign=dQgmvtx%2FB9Zn1ReTjTqTS%2FRo9mA%3D\\\",\\\"coverHorizontalInfo\\\":{\\\"videoName\\\":\\\"广点通能投Soul了,1000曝光6到10块.mp4\\\",\\\"coverEditLogInfo\\\":{\\\"is_cover_edit\\\":\\\"true\\\",\\\"cover_type\\\":\\\"recommend_pic\\\",\\\"is_setting_double_cover\\\":1,\\\"video_cover_source\\\":\\\"recommend_pic\\\",\\\"is_text_template\\\":0,\\\"is_text\\\":0,\\\"text_num\\\":0,\\\"is_cover_template\\\":0,\\\"tab_name\\\":\\\"\\\",\\\"sticker_tab_name\\\":\\\"\\\",\\\"filter_tab_name\\\":\\\"\\\",\\\"text_content\\\":\\\"\\\",\\\"text_template_content\\\":\\\"\\\",\\\"is_use_sticker\\\":0,\\\"is_use_filter\\\":0,\\\"text_template_id\\\":\\\"\\\",\\\"sticker_id\\\":\\\"\\\",\\\"filter_id\\\":[],\\\"second_cover_details\\\":\\\"{\\\\\\\"video_cover_source_landscape\\\\\\\":\\\\\\\"recommend_pic\\\\\\\",\\\\\\\"is_text_template_landscape\\\\\\\":0,\\\\\\\"is_text_landscape\\\\\\\":0,\\\\\\\"text_num_landscape\\\\\\\":0,\\\\\\\"is_cover_template_landscape\\\\\\\":0,\\\\\\\"tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_tab_name_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"text_template_content_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"is_use_sticker_landscape\\\\\\\":0,\\\\\\\"is_use_filter_landscape\\\\\\\":0,\\\\\\\"text_template_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"sticker_id_landscape\\\\\\\":\\\\\\\"\\\\\\\",\\\\\\\"filter_id_landscape\\\\\\\":[]}\\\"},\\\"posterDelay\\\":30.08,\\\"uri\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"customCoverImageHeight\\\":663.3667176961899,\\\"customCoverImageWidth\\\":498,\\\"edited\\\":true,\\\"draft\\\":{\\\"slot\\\":\\\"tos://tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"width\\\":498,\\\"height\\\":663.3667176961899,\\\"templateId\\\":\\\"7293420016291053618\\\",\\\"templateUrl\\\":\\\"https://lf3-media.amemv.com/obj/media-platform-open/4f4f551c-7951-4a29-b3e8-fd628670fc3f\\\"},\\\"type\\\":2,\\\"defaultUri\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"cropedUri\\\":\\\"tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32\\\",\\\"aiGenCoverId\\\":\\\"1b56d2cca057e67967fdf33b021cb4f2\\\",\\\"url\\\":\\\"https://p0-creator-media-private.douyin.com/tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32~tplv-jm8ajry58r-image.jpeg?policy=eyJ2bSI6MywidWlkIjoiOTU1MTkxOTQ4OTcifQ%3D%3D&rk3s=70809c85&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1773061612&x-orig-sign=g5PsdQ6IgpXRgDv1msFdBPHJcrw%3D\\\"},\\\"coverHorizontalUrl\\\":\\\"https://p0-creator-media-private.douyin.com/tos-cn-i-jm8ajry58r/8da1b32963044b589eb8df95875dea32~tplv-jm8ajry58r-image.jpeg?policy=eyJ2bSI6MywidWlkIjoiOTU1MTkxOTQ4OTcifQ%3D%3D&rk3s=70809c85&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1773061612&x-orig-sign=g5PsdQ6IgpXRgDv1msFdBPHJcrw%3D\\\",\\\"pasterInfo\\\":{},\\\"stateInfo\\\":null,\\\"croppedCoverInfo\\\":null,\\\"uploadBackgroundInfo\\\":null,\\\"uploadPasterInfo\\\":null,\\\"uploadCoverStateInfo\\\":null,\\\"xiguaCoverInfo\\\":{\\\"posterDelay\\\":0},\\\"xiguaPasterInfo\\\":null,\\\"xiguaStateInfo\\\":null,\\\"xiguaUploadCoverStateInfo\\\":null,\\\"xiguaUploadBackgroundInfo\\\":null,\\\"xiguaUploadPasterInfo\\\":null,\\\"editXigua\\\":false,\\\"coverSource\\\":\\\"\\\",\\\"previewVideoList\\\":[{\\\"isCurrent\\\":true},{\\\"coverUrl\\\":\\\"https://p26-sign.douyinpic.com/tos-cn-i-0813c000-ce/ocg0yaxIPomXiAWiNAB9HBMG1AqA6BBqPvAVE~tplv-dy-resize-walign-adapt-aq:540:q75.webp?lk3s=138a59ce&x-expires=1774267200&x-signature=qjYI%2FuKfHuas1XFwVfG2ONb8u08%3D&from=327834062&s=PackSourceEnum_PUBLISH&se=false&sc=cover&biz_tag=aweme_video&l=20260309200640335F4D3FFDF9AD98686F\\\",\\\"isTiming\\\":false,\\\"isPreview\\\":false,\\\"isCurrent\\\":false,\\\"isLiveReplay\\\":false},{\\\"coverUrl\\\":\\\"https://p3-sign.douyinpic.com/tos-cn-p-0015c000-ce/oUSBJwDO2QA60FeNDOqiDeC18gIfaDGrSIS7lL~tplv-dy-resize-walign-adapt-aq:540:q75.webp?lk3s=138a59ce&x-expires=1774267200&x-signature=uPBOZEoLPj8mTBeNFmcYEUlCxnU%3D&from=327834062&s=PackSourceEnum_PUBLISH&se=false&sc=cover&biz_tag=aweme_video&l=20260309200640335F4D3FFDF9AD98686F\\\",\\\"isTiming\\\":false,\\\"isPreview\\\":false,\\\"isCurrent\\\":false,\\\"isLiveReplay\\\":false},{\\\"coverUrl\\\":\\\"https://p26-sign.douyinpic.com/tos-cn-p-0015c000-ce/oUOBcIVchfpAiBqxoDEbBFYA9E2DtwdnyEfN1P~tplv-dy-resize-walign-adapt-aq:540:q75.webp?lk3s=138a59ce&x-expires=1774267200&x-signature=YNSTWNaKoa3M7nyGfKwDeFOVbPo%3D&from=327834062&s=PackSourceEnum_PUBLISH&se=false&sc=cover&biz_tag=aweme_vid" + } +] \ No newline at end of file diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_api_publish.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_api_publish.py new file mode 100644 index 00000000..633f9dad --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_api_publish.py @@ -0,0 +1,519 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +抖音创作者中心 - 纯 API 视频发布 +逆向 creator.douyin.com 内部接口,不依赖浏览器自动化 + +流程: + 1. GET /web/api/media/upload/auth/v5/ → 获取上传凭证 (ak, auth) + 2. GET imagex.bytedanceapi.com?Action=ApplyUploadInner → 获取上传地址 + 3. POST {UploadHosts}/upload/v1/{storeUri} → 分片上传视频 + 4. POST /web/api/media/aweme/create/ → 发布作品 +""" + +import asyncio +import datetime +import hashlib +import hmac +import json +import random +import string +import sys +import uuid +import zlib +from pathlib import Path +from urllib.parse import urlencode, quote + +import httpx + +# ── 配置 ─────────────────────────────────────────────────── +COOKIE_FILE = Path(__file__).parent / "douyin_storage_state.json" +CHUNK_SIZE = 3 * 1024 * 1024 # 3MB per chunk + +BASE = "https://creator.douyin.com" +AUTH_URL = f"{BASE}/web/api/media/upload/auth/v5/" +CREATE_URL = f"{BASE}/web/api/media/aweme/create/" +IMAGEX_HOST = "https://imagex.bytedanceapi.com/" + +UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" + + +# ── Cookie 工具 ──────────────────────────────────────────── +def load_cookies_from_storage_state(path: Path) -> str: + """从 Playwright storage_state.json 提取 Cookie 字符串""" + with open(path, "r", encoding="utf-8") as f: + state = json.load(f) + cookies = state.get("cookies", []) + # 只取 creator.douyin.com 和 .douyin.com 的 cookie + parts = [] + for c in cookies: + domain = c.get("domain", "") + if "douyin.com" in domain: + parts.append(f"{c['name']}={c['value']}") + return "; ".join(parts) + + +# ── AWS4-HMAC-SHA256 签名 ────────────────────────────────── +def _hmac_sha256(key: bytes, msg: str) -> bytes: + return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() + + +def get_signing_key(secret: str, date_stamp: str, region: str, service: str) -> bytes: + k_date = _hmac_sha256(("AWS4" + secret).encode("utf-8"), date_stamp) + k_region = _hmac_sha256(k_date, region) + k_service = _hmac_sha256(k_region, service) + k_signing = _hmac_sha256(k_service, "aws4_request") + return k_signing + + +def build_authorization( + access_key_id: str, + secret_access_key: str, + session_token: str, + region: str, + service: str, + canonical_querystring: str, + method: str = "GET", +) -> tuple[str, str, str]: + """生成 AWS4-HMAC-SHA256 Authorization header,返回 (authorization, amz_date, session_token)""" + now = datetime.datetime.now(datetime.timezone.utc) + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + date_stamp = now.strftime("%Y%m%d") + + canonical_headers = ( + f"x-amz-date:{amz_date}\nx-amz-security-token:{session_token}\n" + ) + signed_headers = "x-amz-date;x-amz-security-token" + payload_hash = hashlib.sha256(b"").hexdigest() + + canonical_request = ( + f"{method}\n/\n{canonical_querystring}\n{canonical_headers}\n" + f"{signed_headers}\n{payload_hash}" + ) + + credential_scope = f"{date_stamp}/{region}/{service}/aws4_request" + string_to_sign = ( + f"AWS4-HMAC-SHA256\n{amz_date}\n{credential_scope}\n" + + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest() + ) + + signing_key = get_signing_key(secret_access_key, date_stamp, region, service) + signature = hmac.new( + signing_key, string_to_sign.encode("utf-8"), hashlib.sha256 + ).hexdigest() + + authorization = ( + f"AWS4-HMAC-SHA256 Credential={access_key_id}/{date_stamp}/{region}/{service}/aws4_request, " + f"SignedHeaders={signed_headers}, Signature={signature}" + ) + return authorization, amz_date, session_token + + +def random_s(length: int = 11) -> str: + chars = string.ascii_lowercase + string.digits + return "".join(random.choice(chars) for _ in range(length)) + + +# ── Step 1: 获取上传授权 ────────────────────────────────── +async def get_upload_auth(client: httpx.AsyncClient, cookie: str) -> dict: + print(" [1/4] 获取上传凭证...") + resp = await client.get( + AUTH_URL, + headers={"Cookie": cookie, "User-Agent": UA}, + ) + resp.raise_for_status() + data = resp.json() + if data.get("status_code") != 0: + raise RuntimeError(f"auth 失败: {data}") + ak = data["ak"] + auth_raw = json.loads(data["auth"]) + print(f" ak={ak[:20]}... auth.AccessKeyID={auth_raw['AccessKeyID'][:20]}...") + return { + "ak": ak, + "access_key_id": auth_raw["AccessKeyID"], + "secret_access_key": auth_raw["SecretAccessKey"], + "session_token": auth_raw["SessionToken"], + } + + +# ── Step 2: 获取视频上传分配 ────────────────────────────── +async def apply_upload( + client: httpx.AsyncClient, auth: dict, file_size: int +) -> dict: + print(" [2/4] 获取上传分配地址...") + region = "cn-north-1" + service = "vod" + + params = { + "Action": "ApplyUploadInner", + "FileSize": str(file_size), + "FileType": "video", + "IsInner": "1", + "SpaceName": "aweme", + "Version": "2020-11-19", + "app_id": "2906", + "s": random_s(), + "user_id": "", + } + canonical_qs = "&".join(f"{k}={v}" for k, v in sorted(params.items())) + + authorization, amz_date, session_token = build_authorization( + auth["access_key_id"], + auth["secret_access_key"], + auth["session_token"], + region, + service, + canonical_qs, + ) + + resp = await client.get( + IMAGEX_HOST, + params=params, + headers={ + "authorization": authorization, + "x-amz-date": amz_date, + "x-amz-security-token": session_token, + "User-Agent": UA, + }, + ) + resp.raise_for_status() + data = resp.json() + + if "Result" not in data: + raise RuntimeError(f"ApplyUpload 失败: {data}") + + result = data["Result"] + upload_address = result.get("InnerUploadAddress", result) + session_key = upload_address.get("SessionKey", "") + upload_hosts = upload_address.get("UploadNodes", [{}])[0].get("UploadHost", "") + store_uri = upload_address.get("UploadNodes", [{}])[0].get("StoreInfos", [{}])[0].get("StoreUri", "") + store_auth = upload_address.get("UploadNodes", [{}])[0].get("StoreInfos", [{}])[0].get("Auth", "") + + if not upload_hosts or not store_uri: + # 备用路径 + upload_hosts = result.get("UploadAddress", {}).get("UploadHosts", [""])[0] + store_uri = result.get("UploadAddress", {}).get("StoreInfos", [{}])[0].get("StoreUri", "") + store_auth = result.get("UploadAddress", {}).get("StoreInfos", [{}])[0].get("Auth", "") + session_key = result.get("UploadAddress", {}).get("SessionKey", "") + + print(f" host={upload_hosts}") + print(f" storeUri={store_uri[:40]}...") + print(f" sessionKey={session_key[:30]}...") + return { + "session_key": session_key, + "upload_host": upload_hosts, + "store_uri": store_uri, + "auth": store_auth, + } + + +# ── Step 3: 分片上传视频 ────────────────────────────────── +async def upload_video_chunks( + client: httpx.AsyncClient, upload_info: dict, file_path: str +) -> bool: + print(" [3/4] 分片上传视频...") + data = Path(file_path).read_bytes() + total_size = len(data) + total_chunks = (total_size + CHUNK_SIZE - 1) // CHUNK_SIZE + upload_id = str(uuid.uuid4()) + + base_url = f"https://{upload_info['upload_host']}/upload/v1/{upload_info['store_uri']}" + + for i in range(total_chunks): + start = i * CHUNK_SIZE + end = min((i + 1) * CHUNK_SIZE, total_size) + chunk = data[start:end] + + crc32 = hex(zlib.crc32(chunk) & 0xFFFFFFFF)[2:] + + params = { + "uploadid": upload_id, + "part_number": str(i + 1), + "part_offset": str(start), + "phase": "transfer", + } + + resp = await client.post( + base_url, + params=params, + content=chunk, + headers={ + "Authorization": upload_info["auth"], + "Content-CRC32": crc32, + "Content-Type": "application/octet-stream", + "User-Agent": UA, + }, + timeout=120.0, + ) + + if resp.status_code == 200: + resp_data = resp.json() + if resp_data.get("code") == 2000: + print(f" chunk {i+1}/{total_chunks} 上传成功 (crc32={crc32})") + else: + print(f" chunk {i+1}/{total_chunks} 返回异常: {resp_data}") + return False + else: + print(f" chunk {i+1}/{total_chunks} HTTP {resp.status_code}: {resp.text[:200]}") + return False + + # 分片上传完成后,发送 finish 请求 + finish_params = { + "uploadid": upload_id, + "phase": "finish", + } + finish_resp = await client.post( + base_url, + params=finish_params, + headers={ + "Authorization": upload_info["auth"], + "User-Agent": UA, + }, + timeout=60.0, + ) + print(f" finish 响应: HTTP {finish_resp.status_code}") + try: + finish_data = finish_resp.json() + print(f" finish 数据: {json.dumps(finish_data, ensure_ascii=False)[:200]}") + except Exception: + print(f" finish 原始: {finish_resp.text[:200]}") + + print(f" 视频上传完成: {total_chunks} 个分片, {total_size/1024/1024:.1f}MB") + return True + + +# ── Step 4: 发布作品 ────────────────────────────────────── +def extract_csrf_token(cookie_str: str) -> str: + """从 cookie 字符串中提取 passport_csrf_token""" + for part in cookie_str.split(";"): + kv = part.strip().split("=", 1) + if len(kv) == 2 and kv[0].strip() == "passport_csrf_token": + return kv[1].strip() + return "" + + +async def create_aweme( + client: httpx.AsyncClient, + cookie: str, + store_uri: str, + title: str, + timing: int = -1, + session_key: str = "", +) -> dict: + print(" [4/4] 发布视频作品...") + creation_id = f"{random_s(8)}{int(datetime.datetime.now(datetime.timezone.utc).timestamp() * 1000)}" + + # 构建 form-encoded body + parts = [ + f"text={title}", + "text_extra=[]", + "activity=[]", + "challenges=[]", + 'hashtag_source=""', + "mentions=[]", + "ifLongTitle=true", + "hot_sentence=", + "visibility_type=0", + "download=1", + f"poster={store_uri}", + f"timing={timing}", + f'video={{"uri":"{store_uri}"}}', + f"creation_id={creation_id}", + ] + if session_key: + parts.append(f"session_key={session_key}") + body = "&".join(parts) + + csrf = extract_csrf_token(cookie) + + headers = { + "Cookie": cookie, + "User-Agent": UA, + "Content-Type": "text/plain", + "Referer": "https://creator.douyin.com/creator-micro/content/publish", + "Origin": "https://creator.douyin.com", + "Accept": "application/json, text/plain, */*", + } + if csrf: + headers["X-CSRFToken"] = csrf + + resp = await client.post(CREATE_URL, headers=headers, content=body) + + print(f" HTTP {resp.status_code}, Content-Type: {resp.headers.get('content-type', 'unknown')}") + raw = resp.text + print(f" 原始响应 (前500字): {raw[:500]}") + + if resp.status_code != 200: + return {"status_code": resp.status_code, "error": raw[:200]} + + try: + data = resp.json() + except Exception: + return {"status_code": -1, "error": f"非JSON响应: {raw[:200]}"} + + return data + + +# ── 单条发布主流程 ──────────────────────────────────────── +async def publish_one( + video_path: str, + title: str, + cookie: str, + timing: int = -1, +) -> bool: + file_size = Path(video_path).stat().st_size + print(f"\n{'='*60}") + print(f" 视频: {Path(video_path).name}") + print(f" 大小: {file_size/1024/1024:.1f}MB") + print(f" 标题: {title[:50]}") + if timing > 0: + from datetime import datetime as dt + print(f" 定时: {dt.fromtimestamp(timing).strftime('%Y-%m-%d %H:%M')}") + print(f"{'='*60}") + + async with httpx.AsyncClient(timeout=60.0) as client: + # Step 1 + auth = await get_upload_auth(client, cookie) + + # Step 2 + upload_info = await apply_upload(client, auth, file_size) + + # Step 3 + ok = await upload_video_chunks(client, upload_info, video_path) + if not ok: + print(" [✗] 视频上传失败") + return False + + # Step 4 + result = await create_aweme( + client, cookie, upload_info["store_uri"], title, timing, + session_key=upload_info.get("session_key", ""), + ) + + status = result.get("status_code", -1) + if status == 0: + print(" [✓] 视频发布成功!") + return True + else: + print(f" [!] 发布接口返回: status_code={status}") + print(f" 完整响应: {json.dumps(result, ensure_ascii=False)}") + return False + + +# ── 批量发布 ────────────────────────────────────────────── +VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片") + +TITLES = { + "早起不是为了开派对,是不吵老婆睡觉.mp4": + "早起不是为了开派对,是不吵老婆睡觉。初衷就这一个。#Soul派对 #创业日记 #晨间直播 #私域干货", + "懒人的活法 动作简单有利可图正反馈.mp4": + "懒有懒的活法:动作简单、有利可图、正反馈,就能坐得住。#Soul派对 #副业 #私域 #切片变现", + "初期团队先找两个IS,比钱好使 ENFJ链接人,ENTJ指挥.mp4": + "初期团队先找两个IS,比钱好使。ENFJ链接人,ENTJ指挥。#MBTI #创业团队 #Soul派对", + "ICU出来一年多 活着要在互联网上留下东西.mp4": + "ICU出来一年多,活着要在互联网上留下东西。#人生感悟 #创业 #Soul派对 #记录生活", + "MBTI疗愈SOUL 年轻人测MBTI,40到60岁走五行八卦.mp4": + "年轻人测MBTI,40到60岁走五行八卦。#MBTI #Soul派对 #五行 #疗愈", + "Soul业务模型 派对+切片+小程序全链路.mp4": + "Soul业务模型:派对+切片+小程序全链路。#Soul派对 #商业模式 #私域运营 #小程序", + "Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4": + "Soul切片30秒到8分钟,AI半小时能剪10到30个。#AI剪辑 #Soul派对 #切片变现 #效率工具", + "刷牙听业务逻辑 Soul切片变现怎么跑.mp4": + "刷牙听业务逻辑:Soul切片变现怎么跑。#Soul派对 #切片变现 #副业 #商业逻辑", + "国学易经怎么学 两小时七七八八,召唤作者对话.mp4": + "国学易经怎么学?两小时七七八八,召唤作者对话。#国学 #易经 #Soul派对 #学习方法", + "广点通能投Soul了,1000曝光6到10块.mp4": + "广点通能投Soul了,1000曝光6到10块。#Soul派对 #广点通 #流量投放 #私域获客", + "建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4": + "建立信任不是求来的。卖外挂发邮件三个月拿下德国总代。#销售 #信任 #Soul派对 #商业故事", + "核心就两个字 筛选。能开派对坚持7天的人再谈.mp4": + "核心就两个字:筛选。能开派对坚持7天的人再谈。#筛选 #Soul派对 #创业 #坚持", + "睡眠不好?每天放下一件事,做减法.mp4": + "睡眠不好?每天放下一件事,做减法。#睡眠 #减法 #Soul派对 #生活方式", + "这套体系花了170万,但前端几十块就能参与.mp4": + "这套体系花了170万,但前端几十块就能参与。#商业体系 #Soul派对 #私域 #低成本创业", + "金融AI获客体系 后端30人沉淀12年,前端丢手机.mp4": + "金融AI获客体系:后端30人沉淀12年,前端丢手机。#AI获客 #金融 #Soul派对 #商业模式", +} + + +def get_title(filename: str) -> str: + if filename in TITLES: + return TITLES[filename] + return f"{Path(filename).stem} #Soul派对 #创业日记 #卡若创业派对" + + +async def main(): + if not COOKIE_FILE.exists(): + print("[✗] Cookie 文件不存在,请先运行 douyin_login.py 获取") + return 1 + + cookie = load_cookies_from_storage_state(COOKIE_FILE) + if not cookie: + print("[✗] Cookie 为空") + return 1 + print(f"[✓] Cookie 已加载 ({len(cookie)} chars)") + + # 先测试 Cookie 有效性 + async with httpx.AsyncClient(timeout=15.0) as client: + resp = await client.get( + f"{BASE}/web/api/media/user/info/", + headers={"Cookie": cookie, "User-Agent": UA}, + ) + data = resp.json() + if data.get("status_code") != 0: + print(f"[✗] Cookie 无效: {data}") + return 1 + user = data.get("user_info", {}) + print(f"[✓] 登录用户: {user.get('nickname', 'unknown')}") + + videos = sorted(VIDEO_DIR.glob("*.mp4")) + if not videos: + print("[✗] 未找到视频") + return 1 + print(f"[i] 共 {len(videos)} 条视频\n") + + # 计算定时时间(每小时一条,从当前+2h开始) + import time + now_ts = int(time.time()) + base_ts = now_ts + 2 * 3600 + # 对齐到下一个整点 + base_ts = (base_ts // 3600 + 1) * 3600 + + results = [] + for i, vp in enumerate(videos): + title = get_title(vp.name) + schedule_ts = base_ts + i * 3600 # 每小时一条 + + try: + ok = await publish_one( + video_path=str(vp), + title=title, + cookie=cookie, + timing=schedule_ts, + ) + except Exception as e: + print(f" [✗] 异常: {e}") + ok = False + results.append((vp.name, ok, schedule_ts)) + + if i < len(videos) - 1 and ok: + print(" 等待 5 秒...") + await asyncio.sleep(5) + + # 汇总 + print(f"\n{'='*60}") + print(" 发布汇总") + print(f"{'='*60}") + from datetime import datetime as dt + for name, ok, ts in results: + status = "✓" if ok else "✗" + t = dt.fromtimestamp(ts).strftime("%m-%d %H:%M") + print(f" [{status}] {t} | {name}") + success = sum(1 for _, ok, _ in results if ok) + print(f"\n 成功: {success}/{len(results)}") + return 0 if success == len(results) else 1 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_batch_publish.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_batch_publish.py new file mode 100644 index 00000000..f7927502 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_batch_publish.py @@ -0,0 +1,569 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +抖音批量定时发布(Playwright 自动化) +1. 首次运行弹出浏览器让用户扫码登录,保存 cookie +2. 按每小时一条的节奏定时发布目录下所有 mp4 +""" + +import asyncio +import json +import sys +import os +from datetime import datetime, timedelta +from pathlib import Path + +# ── 万推 backend 路径 ─────────────────────────────────────── +WANTUI_BACKEND = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend") +sys.path.insert(0, str(WANTUI_BACKEND)) + +from playwright.async_api import async_playwright +from utils.base_social_media import set_init_script + +# ── 配置 ─────────────────────────────────────────────────── +VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片") +COOKIE_FILE = Path(__file__).parent / "douyin_storage_state.json" +CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" +HOUR_INTERVAL = 1 # 每条间隔小时数 +START_OFFSET_HOURS = 2 # 第一条从当前时间 +2h 开始(抖音要求 >=2h) + +# ── 视频标题映射(文件名 → 标题+话题) ───────────────────── +TITLES = { + "早起不是为了开派对,是不吵老婆睡觉.mp4": + "早起不是为了开派对,是不吵老婆睡觉。初衷就这一个。#Soul派对 #创业日记 #晨间直播 #私域干货", + "懒人的活法 动作简单有利可图正反馈.mp4": + "懒有懒的活法:动作简单、有利可图、正反馈,就能坐得住。#Soul派对 #副业 #私域 #切片变现", + "初期团队先找两个IS,比钱好使 ENFJ链接人,ENTJ指挥.mp4": + "初期团队先找两个IS,比钱好使。ENFJ链接人,ENTJ指挥。#MBTI #创业团队 #Soul派对", + "ICU出来一年多 活着要在互联网上留下东西.mp4": + "ICU出来一年多,活着要在互联网上留下东西。#人生感悟 #创业 #Soul派对 #记录生活", + "MBTI疗愈SOUL 年轻人测MBTI,40到60岁走五行八卦.mp4": + "年轻人测MBTI,40到60岁走五行八卦。#MBTI #Soul派对 #五行 #疗愈", + "Soul业务模型 派对+切片+小程序全链路.mp4": + "Soul业务模型:派对+切片+小程序全链路。#Soul派对 #商业模式 #私域运营 #小程序", + "Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4": + "Soul切片30秒到8分钟,AI半小时能剪10到30个。#AI剪辑 #Soul派对 #切片变现 #效率工具", + "刷牙听业务逻辑 Soul切片变现怎么跑.mp4": + "刷牙听业务逻辑:Soul切片变现怎么跑。#Soul派对 #切片变现 #副业 #商业逻辑", + "国学易经怎么学 两小时七七八八,召唤作者对话.mp4": + "国学易经怎么学?两小时七七八八,召唤作者对话。#国学 #易经 #Soul派对 #学习方法", + "广点通能投Soul了,1000曝光6到10块.mp4": + "广点通能投Soul了,1000曝光6到10块。#Soul派对 #广点通 #流量投放 #私域获客", + "建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4": + "建立信任不是求来的。卖外挂发邮件三个月拿下德国总代。#销售 #信任 #Soul派对 #商业故事", + "核心就两个字 筛选。能开派对坚持7天的人再谈.mp4": + "核心就两个字:筛选。能开派对坚持7天的人再谈。#筛选 #Soul派对 #创业 #坚持", + "睡眠不好?每天放下一件事,做减法.mp4": + "睡眠不好?每天放下一件事,做减法。#睡眠 #减法 #Soul派对 #生活方式", + "这套体系花了170万,但前端几十块就能参与.mp4": + "这套体系花了170万,但前端几十块就能参与。#商业体系 #Soul派对 #私域 #低成本创业", + "金融AI获客体系 后端30人沉淀12年,前端丢手机.mp4": + "金融AI获客体系:后端30人沉淀12年,前端丢手机。#AI获客 #金融 #Soul派对 #商业模式", +} + + +def get_title(filename: str) -> str: + if filename in TITLES: + return TITLES[filename] + stem = Path(filename).stem + return f"{stem} #Soul派对 #创业日记 #卡若创业派对" + + +def parse_tags_from_title(title: str) -> list[str]: + """从标题中提取 # 话题""" + tags = [] + for part in title.split("#"): + t = part.strip().split()[0] if part.strip() else "" + if t and t != title.split("#")[0].strip(): + tags.append(t) + return tags + + +# ── Cookie 管理 ────────────────────────────────────────── +async def ensure_cookie() -> bool: + """检查 cookie 是否有效,无效则弹窗让用户登录""" + if COOKIE_FILE.exists(): + valid = await check_cookie_valid() + if valid: + print("[✓] Cookie 有效,跳过登录") + return True + print("[!] Cookie 已失效,需要重新登录") + + print("\n" + "=" * 60) + print(" 即将弹出浏览器窗口,请在浏览器中扫码登录抖音") + print(" 登录成功后,在 Playwright Inspector 中点击绿色 ▶ 按钮") + print("=" * 60 + "\n") + + async with async_playwright() as pw: + browser = await pw.chromium.launch( + headless=False, + executable_path=CHROME_PATH if os.path.exists(CHROME_PATH) else None, + ) + context = await browser.new_context() + context = await set_init_script(context) + page = await context.new_page() + await page.goto("https://creator.douyin.com/") + await page.pause() # 暂停等用户登录后点继续 + await context.storage_state(path=str(COOKIE_FILE)) + await context.close() + await browser.close() + + print("[✓] Cookie 已保存") + return True + + +async def check_cookie_valid() -> bool: + async with async_playwright() as pw: + browser = await pw.chromium.launch(headless=True) + context = await browser.new_context(storage_state=str(COOKIE_FILE)) + context = await set_init_script(context) + page = await context.new_page() + try: + await page.goto( + "https://creator.douyin.com/creator-micro/content/upload", + wait_until="domcontentloaded", + timeout=30000, + ) + await page.wait_for_url("**/creator.douyin.com/**/upload**", timeout=10000) + await asyncio.sleep(3) + if ( + await page.get_by_text("手机号登录").count() + or await page.get_by_text("扫码登录").count() + ): + await context.close() + await browser.close() + return False + await context.close() + await browser.close() + return True + except Exception: + await context.close() + await browser.close() + return False + + +# ── 辅助函数 ────────────────────────────────────────────── +async def dismiss_popups(page): + """关闭抖音创作者中心可能出现的各种弹窗""" + # "视频预览功能" 弹窗 + try: + btn = page.get_by_text("我知道了", exact=True) + if await btn.count() and await btn.first.is_visible(): + await btn.first.click() + await asyncio.sleep(0.5) + print(" [i] 关闭了视频预览功能弹窗") + except Exception: + pass + + # 话题下拉菜单 → Escape 关闭 + try: + await page.keyboard.press("Escape") + await asyncio.sleep(0.3) + except Exception: + pass + + # 日历弹窗 → 点击空白处 + try: + await page.mouse.click(10, 10) + await asyncio.sleep(0.3) + except Exception: + pass + + # "添加共创"提示弹窗 → 点击空白关闭 + try: + tooltip = page.locator('[class*="tooltip"]').first + if await tooltip.count() and await tooltip.is_visible(): + await page.mouse.click(10, 10) + await asyncio.sleep(0.3) + except Exception: + pass + + # 任何 semi-modal 确认弹窗 + try: + close_btn = page.locator('.semi-modal-close') + if await close_btn.count() and await close_btn.first.is_visible(): + await close_btn.first.click() + await asyncio.sleep(0.3) + except Exception: + pass + + +async def set_cover(page): + """尝试设置封面(点击"选择封面"→ 选第一个推荐封面 → 完成)""" + try: + # 点击"选择封面" + cover_btn = page.get_by_text("选择封面", exact=False) + if not await cover_btn.count(): + return + await cover_btn.first.click() + await asyncio.sleep(2) + + # 等待封面弹窗 + modal = page.locator("div.dy-creator-content-modal") + if not await modal.count(): + # 尝试另一种选择器 + modal = page.locator('[class*="modal"]') + + # 选择推荐的第一个封面 + recommend = page.locator('[class^="recommendCover-"]').first + if await recommend.count(): + await recommend.click() + await asyncio.sleep(1) + print(" [i] 已选择推荐封面") + else: + # 备选:选择第一个可用封面图 + covers = page.locator('[class*="cover-item"], [class*="coverItem"]').first + if await covers.count(): + await covers.click() + await asyncio.sleep(1) + + # 点击"完成"按钮 + finish_btn = page.get_by_role("button", name="完成") + if await finish_btn.count() and await finish_btn.first.is_visible(): + await finish_btn.first.click() + await asyncio.sleep(1) + print(" [✓] 封面设置完成") + else: + # 备选:按 Escape 关闭 + await page.keyboard.press("Escape") + await asyncio.sleep(0.5) + except Exception as e: + print(f" [i] 封面设置跳过: {e}") + try: + await page.keyboard.press("Escape") + except Exception: + pass + + +# ── 单条视频上传 ───────────────────────────────────────── +async def upload_one( + video_path: str, + title: str, + tags: list[str], + publish_date: datetime | None, + idx: int, + total: int, +) -> bool: + print(f"\n{'─' * 60}") + print(f" [{idx}/{total}] {Path(video_path).name}") + print(f" 标题: {title[:50]}...") + if publish_date: + print(f" 定时: {publish_date.strftime('%Y-%m-%d %H:%M')}") + print(f"{'─' * 60}") + + async with async_playwright() as pw: + browser = await pw.chromium.launch( + headless=False, + executable_path=CHROME_PATH if os.path.exists(CHROME_PATH) else None, + ) + context = await browser.new_context(storage_state=str(COOKIE_FILE)) + context = await set_init_script(context) + page = await context.new_page() + + try: + # 1. 打开上传页 + await page.goto( + "https://creator.douyin.com/creator-micro/content/upload", + wait_until="domcontentloaded", + timeout=60000, + ) + await page.wait_for_url("**/creator.douyin.com/**/upload**", timeout=60000) + await page.wait_for_load_state("load", timeout=20000) + try: + await page.get_by_text("上传视频", exact=False).first.wait_for( + state="visible", timeout=15000 + ) + except Exception: + pass + await asyncio.sleep(3) + + # 2. 上传文件 + upload_selectors = [ + "input[type='file']", + "div[class^='container'] input", + "[class*='upload'] input[type='file']", + "div[class*='upload'] input", + "[class^='upload-btn-input']", + "div.progress-div input", + ] + uploaded = False + for selector in upload_selectors: + try: + loc = page.locator(selector).first + await loc.wait_for(state="attached", timeout=5000) + await loc.set_input_files(video_path, timeout=120000) + print(f" [✓] 文件已选择 (via {selector})") + uploaded = True + break + except Exception: + continue + if not uploaded: + print(" [✗] 未找到上传输入框") + return False + + # 3. 等待跳转到发布页 + for _ in range(120): + try: + await page.wait_for_url( + "https://creator.douyin.com/creator-micro/content/publish?enter_from=publish_page", + timeout=2000, + ) + print(" [✓] 进入发布页 v1") + break + except Exception: + try: + await page.wait_for_url( + "https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page", + timeout=2000, + ) + print(" [✓] 进入发布页 v2") + break + except Exception: + await asyncio.sleep(1) + else: + print(" [✗] 等待发布页超时") + return False + + await asyncio.sleep(5) + + # 4. 关闭可能的弹窗(多次尝试) + for _ in range(3): + await dismiss_popups(page) + await asyncio.sleep(1) + + # 5. 填写标题(使用 placeholder 选择器,更稳定) + title_filled = False + title_sel = 'input[placeholder*="标题"]' + try: + title_el = page.locator(title_sel).first + if await title_el.count() and await title_el.is_visible(): + await title_el.click() + await title_el.fill(title[:30]) + title_filled = True + except Exception: + pass + if not title_filled: + try: + title_el = page.locator("input.semi-input").first + await title_el.click() + await title_el.fill(title[:30]) + title_filled = True + except Exception as e: + print(f" [!] 标题填写失败: {e}") + if title_filled: + print(" [✓] 标题已填写") + + # 6. 填写描述和话题(在 .zone-container / .notranslate 区域) + desc_zone = page.locator(".zone-container") + try: + await desc_zone.click() + await asyncio.sleep(0.3) + # 先清空 + await page.keyboard.press("Meta+KeyA") + await page.keyboard.press("Delete") + await asyncio.sleep(0.2) + # 写入标题全文(含话题标签)作为描述 + await page.keyboard.type(title, delay=20) + await asyncio.sleep(0.5) + except Exception as e: + print(f" [!] 描述填写异常: {e}") + # 关闭话题下拉 + await page.keyboard.press("Escape") + await asyncio.sleep(0.3) + await page.mouse.click(10, 10) + await asyncio.sleep(0.5) + print(" [✓] 描述和话题已填写") + + # 7. 等待视频上传完毕 + for _ in range(180): + try: + n = await page.locator( + '[class^="long-card"] div:has-text("重新上传")' + ).count() + if n > 0: + print(" [✓] 视频上传完毕") + break + if await page.locator( + 'div.progress-div > div:has-text("上传失败")' + ).count(): + print(" [!] 上传失败,重试") + await page.locator( + 'div.progress-div [class^="upload-btn-input"]' + ).set_input_files(video_path) + except Exception: + pass + await asyncio.sleep(2) + else: + print(" [✗] 视频上传超时(6分钟)") + + # 8. 设置封面(选择智能推荐的第一个) + await set_cover(page) + + # 9. 再次关闭弹窗 + await dismiss_popups(page) + + # 10. 定时发布 + if publish_date: + try: + label = page.locator("[class^='radio']:has-text('定时发布')") + await label.click() + await asyncio.sleep(1) + date_str = publish_date.strftime("%Y-%m-%d %H:%M") + date_input = page.locator('.semi-input[placeholder="日期和时间"]') + await date_input.click() + await asyncio.sleep(0.5) + # 使用 Meta+A(macOS 全选)清空 + await page.keyboard.press("Meta+KeyA") + await page.keyboard.type(date_str) + await page.keyboard.press("Enter") + await asyncio.sleep(1) + # 点击空白处关闭日历 + await page.mouse.click(10, 10) + await asyncio.sleep(0.5) + print(f" [✓] 定时发布设置: {date_str}") + except Exception as e: + print(f" [!] 定时设置失败: {e},将立即发布") + + # 11. 同步到今日头条/西瓜 + try: + third = '[class^="info"] > [class^="first-part"] div div.semi-switch' + if await page.locator(third).count(): + cls = await page.eval_on_selector(third, "div => div.className") + if "semi-switch-checked" not in cls: + await page.locator(third).locator( + "input.semi-switch-native-control" + ).click() + except Exception: + pass + + # 12. 最终关闭所有弹窗再发布 + await dismiss_popups(page) + await asyncio.sleep(1) + + # 13. 点击发布 + published = False + for attempt in range(20): + try: + if attempt < 2: + ss_path = Path(__file__).parent / f"pre_publish_{idx}_a{attempt}.png" + await page.screenshot(path=str(ss_path), full_page=True) + print(f" [i] 截图: {ss_path.name}") + + btn = page.get_by_role("button", name="发布", exact=True) + btn_count = await btn.count() + if btn_count: + await btn.scroll_into_view_if_needed() + await asyncio.sleep(0.3) + await btn.click() + print(f" [i] 已点击发布 (attempt {attempt+1})") + try: + await page.wait_for_url( + "https://creator.douyin.com/creator-micro/content/manage**", + timeout=8000, + ) + print(" [✓] 视频发布成功!") + published = True + break + except Exception: + cur = page.url + print(f" [i] 未跳转,URL: {cur}") + sms_count = await page.get_by_text("验证码").count() + if sms_count: + print(" [!] 需要短信验证,截图保存") + ss = Path(__file__).parent / f"sms_{idx}.png" + await page.screenshot(path=str(ss), full_page=True) + except Exception as e: + print(f" [!] 异常: {e}") + + # 处理封面弹窗 + try: + if await page.get_by_text("请设置封面后再发布").first.is_visible(): + print(" [i] 需要封面,自动选择") + await set_cover(page) + except Exception: + pass + # 关闭其他弹窗 + await dismiss_popups(page) + await asyncio.sleep(3) + + if not published: + print(" [!] 发布超时") + ss_path = Path(__file__).parent / f"timeout_{idx}.png" + await page.screenshot(path=str(ss_path), full_page=True) + print(f" 截图: {ss_path}") + + # 保存更新后的 cookie + await context.storage_state(path=str(COOKIE_FILE)) + + except Exception as e: + print(f" [✗] 异常: {e}") + ss_path = Path(__file__).parent / f"error_{idx}.png" + try: + await page.screenshot(path=str(ss_path), full_page=True) + except Exception: + pass + return False + finally: + await context.close() + await browser.close() + + return True + + +# ── 主流程 ────────────────────────────────────────────── +async def main(): + # 1. 确保 Cookie + ok = await ensure_cookie() + if not ok: + print("[✗] 无法获取有效 Cookie,退出") + return 1 + + # 2. 收集视频列表 + videos = sorted(VIDEO_DIR.glob("*.mp4")) + if not videos: + print("[✗] 未找到任何 mp4 文件") + return 1 + print(f"\n[i] 共发现 {len(videos)} 条视频,准备批量发布\n") + + # 3. 计算定时发布时间 + now = datetime.now() + base_time = now + timedelta(hours=START_OFFSET_HOURS) + # 对齐到整点 + base_time = base_time.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1) + + results = [] + for i, vp in enumerate(videos, 1): + title = get_title(vp.name) + tags = parse_tags_from_title(title) + pub_time = base_time + timedelta(hours=(i - 1) * HOUR_INTERVAL) + + ok = await upload_one( + video_path=str(vp), + title=title, + tags=tags, + publish_date=pub_time, + idx=i, + total=len(videos), + ) + results.append((vp.name, ok, pub_time)) + + if i < len(videos): + print(" ⏳ 等待 10 秒后处理下一条...") + await asyncio.sleep(10) + + # 4. 汇总 + print("\n" + "=" * 60) + print(" 发布汇总") + print("=" * 60) + for name, ok, t in results: + status = "✓" if ok else "✗" + print(f" [{status}] {t.strftime('%m-%d %H:%M')} | {name}") + success = sum(1 for _, ok, _ in results if ok) + print(f"\n 成功: {success}/{len(results)}") + print("=" * 60) + + return 0 if success == len(results) else 1 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_cookie.txt b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_cookie.txt new file mode 100644 index 00000000..121b37cd --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_cookie.txt @@ -0,0 +1 @@ +bd_ticket_guard_client_web_domain=2; passport_csrf_token=7c77660e1f88141e7e90b71d7e32ad0b; passport_csrf_token_default=7c77660e1f88141e7e90b71d7e32ad0b; enter_pc_once=1; UIFID_TEMP=08f4fe5163774c2300555b455fb414e93ca9fbb91678792eb055c0a8974f001d75b51090fcb52c4aaaf9073dc832c6dd4dc3de49b908072111108b86524fe9c74b5a387ca37e4f4839735270d224cafe; bd_ticket_guard_client_data_v2=eyJyZWVfcHVibGljX2tleSI6IkJJdjZnajZiZjBpM29qRW5teWZwOGhqS1ZzRUU4a0lBK1JhR00ycTg4alRQdCtkdkNwcllYNytWb3VJa0k1R2ZWNzJhNDFqNERHZmZ1ZjVmdzhWSnhmQT0iLCJyZXFfY29udGVudCI6InNlY190cyIsInJlcV9zaWduIjoieG1HQ0taTmU5U2dlNkdqaDBlTDE0UFVOUDlYbktOeDhlTzZXUkh5dnZYQT0iLCJzZWNfdHMiOiIjUFlpN0lMZTdjRERuK09KSk02V05HNDErdFJqT1RaSEVnUkxqWkZWY0ZibGdFNWVFem51bm43bkZZQUJ6In0%3D; gfkadpd=2906,33638; _tea_utm_cache_2906=undefined; csrf_session_id=6b09d5b10c4ab498c588892c745a109c; biz_trace_id=45d6e984; passport_assist_user=CjzV-fLLRY-_ejIbeLJs90LfRNLuZvbI0xfqJF0GcZOZpX0hTcWo0kM00noByoHqEe1tfarXTWR6xuZvnIAaSgo8AAAAAAAAAAAAAFApNKm_uePs4rUqvWqP9f6CCWCAV30CZ9yI7jKc732ca5xgSRkDKFG6MrndTOgNN-kcEITRiw4Yia_WVCABIgEDRk9Xvg%3D%3D; _bd_ticket_crypt_doamin=2; _bd_ticket_crypt_cookie=7356de5b4441a9b6a8bdebc21d2974e9; __security_mc_1_s_sdk_sign_data_key_web_protect=dfc9fff6-410f-8e60; __security_mc_1_s_sdk_cert_key=e39ec87f-4583-ad3e; __security_mc_1_s_sdk_crypt_sdk=3aeacc1d-4bd6-aa35; __security_server_data_status=1; x-web-secsdk-uid=13353cdf-41f2-4dd2-bbc5-ddf08884fd66; bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWl0ZXJhdGlvbi12ZXJzaW9uIjoxLCJiZC10aWNrZXQtZ3VhcmQtcmVlLXB1YmxpYy1rZXkiOiJCSXY2Z2o2YmYwaTNvakVubXlmcDhoaktWc0VFOGtJQStSYUdNMnE4OGpUUHQrZHZDcHJZWDcrVm91SWtJNUdmVjcyYTQxajRER2ZmdWY1Znc4Vkp4ZkE9IiwiYmQtdGlja2V0LWd1YXJkLXdlYi12ZXJzaW9uIjoyfQ%3D%3D; passport_fe_beating_status=true diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_hybrid_publish.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_hybrid_publish.py new file mode 100644 index 00000000..4bdfa2b6 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_hybrid_publish.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 +""" +抖音视频批量发布 — 混合方案 v2 +- Playwright 加载页面 + set_input_files 上传视频 +- JS hook 从 transend/enable URL 捕获 video_id +- page.evaluate(fetch) 调用 create_v2 发布 +- 回退方案:浏览器点击发布 +- 命令行全自动 +""" +import asyncio +import json +import os +import random +import string +import sys +import time +from datetime import datetime +from pathlib import Path + +from playwright.async_api import async_playwright + +SCRIPT_DIR = Path(__file__).parent +COOKIE_FILE = SCRIPT_DIR / "douyin_storage_state.json" +STEALTH_JS = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend/utils/stealth.min.js") +CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" +VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片") + +TITLES = { + "早起不是为了开派对,是不吵老婆睡觉.mp4": + "早起不是为了开派对,是不吵老婆睡觉。初衷就这一个。#Soul派对 #创业日记 #晨间直播 #私域干货", + "懒人的活法 动作简单有利可图正反馈.mp4": + "懒有懒的活法:动作简单、有利可图、正反馈,就能坐得住。#Soul派对 #副业 #私域 #切片变现", + "初期团队先找两个IS,比钱好使 ENFJ链接人,ENTJ指挥.mp4": + "初期团队先找两个IS,比钱好使。ENFJ链接人,ENTJ指挥。#MBTI #创业团队 #Soul派对", + "ICU出来一年多 活着要在互联网上留下东西.mp4": + "ICU出来一年多,活着要在互联网上留下东西。#人生感悟 #创业 #Soul派对 #记录生活", + "MBTI疗愈SOUL 年轻人测MBTI,40到60岁走五行八卦.mp4": + "年轻人测MBTI,40到60岁走五行八卦。#MBTI #Soul派对 #五行 #疗愈", + "Soul业务模型 派对+切片+小程序全链路.mp4": + "Soul业务模型:派对+切片+小程序全链路。#Soul派对 #商业模式 #私域运营 #小程序", + "Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4": + "Soul切片30秒到8分钟,AI半小时能剪10到30个。#AI剪辑 #Soul派对 #切片变现 #效率工具", + "刷牙听业务逻辑 Soul切片变现怎么跑.mp4": + "刷牙听业务逻辑:Soul切片变现怎么跑。#Soul派对 #切片变现 #副业 #商业逻辑", + "国学易经怎么学 两小时七七八八,召唤作者对话.mp4": + "国学易经怎么学?两小时七七八八,召唤作者对话。#国学 #易经 #Soul派对 #学习方法", + "广点通能投Soul了,1000曝光6到10块.mp4": + "广点通能投Soul了,1000曝光6到10块。#Soul派对 #广点通 #流量投放 #私域获客", + "建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4": + "建立信任不是求来的。卖外挂发邮件三个月拿下德国总代。#销售 #信任 #Soul派对 #商业故事", + "核心就两个字 筛选。能开派对坚持7天的人再谈.mp4": + "核心就两个字:筛选。能开派对坚持7天的人再谈。#筛选 #Soul派对 #创业 #坚持", + "睡眠不好?每天放下一件事,做减法.mp4": + "睡眠不好?每天放下一件事,做减法。#睡眠 #减法 #Soul派对 #生活方式", + "这套体系花了170万,但前端几十块就能参与.mp4": + "这套体系花了170万,但前端几十块就能参与。#商业体系 #Soul派对 #私域 #低成本创业", + "金融AI获客体系 后端30人沉淀12年,前端丢手机.mp4": + "金融AI获客体系:后端30人沉淀12年,前端丢手机。#AI获客 #金融 #Soul派对 #商业模式", +} + +HOOK_JS = r""" +(function() { + if (window.__dy_hook_installed) return; + window.__dy_hook_installed = true; + window.__dy_video_ids = []; + window.__dy_responses = []; + + const _origFetch = window.fetch; + window.fetch = async function(...args) { + const resp = await _origFetch.apply(this, args); + try { + const url = typeof args[0] === 'string' ? args[0] : (args[0] && args[0].url) || ''; + const urlVidMatch = url.match(/video_id=(v0[a-zA-Z0-9]+)/); + if (urlVidMatch && !window.__dy_video_ids.includes(urlVidMatch[1])) { + window.__dy_video_ids.push(urlVidMatch[1]); + console.log('[HOOK] video_id: ' + urlVidMatch[1]); + } + if (url.includes('vod.bytedance') || url.includes('video/enable') || + url.includes('video/transend') || url.includes('ApplyUpload') || + url.includes('CommitUpload')) { + const clone = resp.clone(); + const text = await clone.text(); + window.__dy_responses.push({ url: url.substring(0, 500), body: text.substring(0, 2000) }); + const vidMatch = text.match(/"(?:video_id|VideoId)":\s*"(v0[a-zA-Z0-9]+)"/); + if (vidMatch && !window.__dy_video_ids.includes(vidMatch[1])) { + window.__dy_video_ids.push(vidMatch[1]); + } + } + } catch(e) {} + return resp; + }; +})(); +""" + + +def get_title(filename: str) -> str: + return TITLES.get(filename, f"{Path(filename).stem} #Soul派对 #创业日记 #卡若创业派对") + + +def random_creation_id() -> str: + chars = string.ascii_lowercase + string.digits + return "".join(random.choices(chars, k=8)) + str(int(time.time() * 1000)) + + +async def publish_one(context, video_path: str, title: str, timing_ts: int, idx: int, total: int) -> bool: + page = await context.new_page() + timing_str = datetime.fromtimestamp(timing_ts).strftime("%Y-%m-%d %H:%M") if timing_ts > 0 else "立即" + filename = Path(video_path).name + + print(f"\n{'='*60}") + print(f" [{idx}/{total}] {filename}") + print(f" 标题: {title[:60]}") + print(f" 定时: {timing_str}") + print(f"{'='*60}") + + try: + # 1. 打开上传页 + print(f" [1] 打开上传页...") + await page.goto("https://creator.douyin.com/creator-micro/content/upload", + wait_until="domcontentloaded", timeout=60000) + await page.wait_for_url("**/upload**", timeout=60000) + await page.wait_for_load_state("load", timeout=20000) + + if await page.get_by_text("手机号登录").count() or await page.get_by_text("扫码登录").count(): + print(f" [!] Cookie 失效") + return False + await asyncio.sleep(3) + + # 2. 上传 + print(f" [2] 上传视频...") + loc = page.locator("input[type='file']").first + await loc.wait_for(state="attached", timeout=10000) + await loc.set_input_files(video_path, timeout=60000) + print(f" [2] OK") + + # 3. 等待发布页 + for _ in range(120): + if "publish" in page.url or "post/video" in page.url: + break + await asyncio.sleep(1) + print(f" [3] 发布页就绪") + + await asyncio.sleep(5) + + # 4. 等待 video_id(hook 已通过 add_init_script 注入) + print(f" [4] 等待 video_id + 转码...") + video_id = None + for i in range(180): + vids = await page.evaluate("window.__dy_video_ids || []") + if vids: + video_id = vids[-1] + print(f" [4] ✓ video_id: {video_id}") + break + if i % 20 == 0 and i > 0: + print(f" ...{i}s") + await asyncio.sleep(1) + + if not video_id: + # 从捕获的响应 URL 中提取 + resps = await page.evaluate("window.__dy_responses || []") + for r in resps: + import re + m = re.search(r'video_id=(v0[a-zA-Z0-9]+)', r.get('url', '')) + if m: + video_id = m.group(1) + print(f" [4] ✓ video_id (from response URL): {video_id}") + break + + # 等待转码完成(再等一会) + if video_id: + await asyncio.sleep(10) + + # 5. 获取 poster + poster = await page.evaluate(r""" + () => { + const imgs = document.querySelectorAll('img[src*="tos-cn-i-"]'); + for (const img of imgs) { + const match = img.src.match(/(tos-cn-i-[a-zA-Z0-9]+\/[a-f0-9]+)/); + if (match) return match[1]; + } + return ''; + } + """) + + # 6. 发布 + if video_id: + print(f" [5] 通过 fetch 调用 create_v2...") + creation_id = random_creation_id() + body = { + "item": { + "common": { + "text": title, + "caption": title, + "item_title": "", + "activity": "[]", + "text_extra": "[]", + "challenges": "[]", + "mentions": "[]", + "hashtag_source": "", + "hot_sentence": "", + "interaction_stickers": "[]", + "visibility_type": 0, + "download": 1, + "timing": timing_ts if timing_ts > 0 else 0, + "creation_id": creation_id, + "media_type": 4, + "video_id": video_id, + "music_source": 0, + "music_id": None, + }, + "cover": { + "custom_cover_image_height": 0, + "custom_cover_image_width": 0, + "poster": poster or "", + "poster_delay": 0, + }, + } + } + body_json = json.dumps(body, ensure_ascii=False) + + result = await page.evaluate(f""" + async () => {{ + try {{ + const resp = await fetch('/web/api/media/aweme/create_v2/', {{ + method: 'POST', + credentials: 'include', + headers: {{ + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/plain, */*', + }}, + body: JSON.stringify({body_json}), + }}); + const text = await resp.text(); + return {{ status: resp.status, body: text.substring(0, 3000) }}; + }} catch(e) {{ + return {{ error: e.message }}; + }} + }} + """) + + resp_body = result.get("body", "") + if resp_body: + try: + parsed = json.loads(resp_body) + if parsed.get("status_code") == 0: + print(f" [✓] API 发布成功!") + return True + else: + print(f" [!] API 返回: {parsed.get('status_msg', resp_body[:100])}") + except Exception: + pass + + print(f" [!] API 发布失败,回退到浏览器...") + + # 回退:浏览器直接发布 + return await _browser_publish(page, title, timing_ts) + + except Exception as e: + print(f" [!] 异常: {e}") + try: + return await _browser_publish(page, title, timing_ts) + except Exception as e2: + print(f" [!] 回退也失败: {e2}") + return False + finally: + await page.close() + + +async def _browser_publish(page, title: str, timing_ts: int) -> bool: + try: + # 填标题 + nl = page.locator(".notranslate").first + await nl.click(timeout=5000) + await page.keyboard.press("Meta+KeyA") + await page.keyboard.press("Delete") + await page.keyboard.type(title[:50], delay=20) + await asyncio.sleep(2) + + for attempt in range(8): + try: + pub = page.get_by_role("button", name="发布", exact=True) + if await pub.count(): + await pub.click() + print(f" 点击发布 (attempt {attempt+1})") + + await page.wait_for_url("**/content/manage**", timeout=12000) + print(f" [✓] 浏览器发布成功!") + return True + except Exception: + try: + if await page.get_by_text("请设置封面后再发布").first.is_visible(): + covers = page.locator('[class*="recommendCover"], [class*="cover-select"]') + if await covers.count() > 0: + await covers.first.click() + await asyncio.sleep(2) + confirm = page.get_by_role("button", name="确定") + if await confirm.count(): + await confirm.click() + await asyncio.sleep(2) + continue + except Exception: + pass + await asyncio.sleep(3) + return False + except Exception as e: + print(f" [!] 浏览器发布异常: {e}") + return False + + +async def main(): + if not COOKIE_FILE.exists(): + print("[!] Cookie 不存在,请先运行 douyin_login.py") + return 1 + + videos = sorted(VIDEO_DIR.glob("*.mp4")) + if not videos: + print("[!] 未找到视频") + return 1 + print(f"[i] 共 {len(videos)} 条视频") + + now_ts = int(time.time()) + base_ts = ((now_ts + 3600) // 3600 + 1) * 3600 + + schedule = [] + for i, vp in enumerate(videos): + ts = base_ts + i * 3600 + title = get_title(vp.name) + schedule.append((vp, title, ts)) + dt_str = datetime.fromtimestamp(ts).strftime("%m-%d %H:%M") + print(f" {i+1:2d}. {dt_str} | {vp.name[:50]}") + + print(f"\n[i] 启动浏览器...") + async with async_playwright() as pw: + browser = await pw.chromium.launch( + headless=False, + executable_path=CHROME if os.path.exists(CHROME) else None, + ) + context = await browser.new_context(storage_state=str(COOKIE_FILE)) + if STEALTH_JS.exists(): + await context.add_init_script(path=str(STEALTH_JS)) + await context.add_init_script(script=HOOK_JS) + + # 验证 Cookie + check = await context.new_page() + await check.goto("https://creator.douyin.com/creator-micro/home", + wait_until="domcontentloaded", timeout=30000) + if await check.get_by_text("手机号登录").count() or await check.get_by_text("扫码登录").count(): + print("[!] Cookie 失效") + await browser.close() + return 1 + await check.close() + print("[✓] Cookie 有效\n") + + results = [] + for i, (vp, title, ts) in enumerate(schedule): + ok = await publish_one(context, str(vp), title, ts, i + 1, len(schedule)) + results.append((vp.name, ok, ts)) + await context.storage_state(path=str(COOKIE_FILE)) + + if i < len(schedule) - 1 and ok: + print(f" 等待 10s...") + await asyncio.sleep(10) + + await context.close() + await browser.close() + + print(f"\n{'='*60}") + print(" 发布汇总") + print(f"{'='*60}") + for name, ok, ts in results: + s = "✓" if ok else "✗" + t = datetime.fromtimestamp(ts).strftime("%m-%d %H:%M") + print(f" [{s}] {t} | {name}") + success = sum(1 for _, ok, _ in results if ok) + print(f"\n 成功: {success}/{len(results)}") + return 0 if success == len(results) else 1 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_login.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_login.py new file mode 100644 index 00000000..a47d8d98 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_login.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""获取抖音 Cookie - 弹窗浏览器 → 扫码登录 → 保存 storage_state""" +import asyncio +import sys +from pathlib import Path + +WANTUI = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend") +sys.path.insert(0, str(WANTUI)) + +from playwright.async_api import async_playwright +from utils.base_social_media import set_init_script + +COOKIE_FILE = Path(__file__).parent / "douyin_storage_state.json" + + +async def main(): + print("即将弹出浏览器,请扫码登录抖音创作者中心。") + print("登录成功后,在 Playwright Inspector 窗口中点击绿色 ▶ 按钮。\n") + + async with async_playwright() as pw: + browser = await pw.chromium.launch(headless=False) + context = await browser.new_context() + context = await set_init_script(context) + page = await context.new_page() + await page.goto("https://creator.douyin.com/", timeout=60000) + await page.pause() + await context.storage_state(path=str(COOKIE_FILE)) + await context.close() + await browser.close() + + print(f"\n[✓] Cookie 已保存到: {COOKIE_FILE}") + print(f" 文件大小: {COOKIE_FILE.stat().st_size} bytes") + print("现在可以运行 douyin_batch_publish.py 批量发布了。") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_oauth_then_publish.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_oauth_then_publish.py new file mode 100644 index 00000000..6da098f1 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_oauth_then_publish.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +抖音 OAuth 授权后立即发布 119 场成片。 +需设置环境变量:DOUYIN_CLIENT_KEY、DOUYIN_CLIENT_SECRET; +可选 DOUYIN_REDIRECT_URI(默认 http://127.0.0.1:8765/callback,需在开放平台应用里配置同值)。 +会打开浏览器,用户登录抖音并点击授权后,自动保存 token 并执行批量发布。 +""" +import json +import os +import subprocess +import sys +import threading +import webbrowser +from http.server import HTTPServer, BaseHTTPRequestHandler +from pathlib import Path +from urllib.parse import parse_qs, urlparse + +try: + import requests +except ImportError: + print("请安装 requests: pip install requests", file=sys.stderr) + sys.exit(1) + +SCRIPT_DIR = Path(__file__).resolve().parent +REDIRECT_PORT = 8765 +REDIRECT_URI = os.environ.get("DOUYIN_REDIRECT_URI") or f"http://127.0.0.1:{REDIRECT_PORT}/callback" +TOKEN_URL = "https://open.douyin.com/oauth/access_token/" + + +def get_client_creds(): + # 支持从 .env 读取(若存在 python-dotenv) + try: + from dotenv import load_dotenv + load_dotenv(SCRIPT_DIR / ".env") + except ImportError: + pass + key = os.environ.get("DOUYIN_CLIENT_KEY") + secret = os.environ.get("DOUYIN_CLIENT_SECRET") + return key, secret + + +class CallbackHandler(BaseHTTPRequestHandler): + code = None + + def do_GET(self): + parsed = urlparse(self.path) + if parsed.path == "/callback": + qs = parse_qs(parsed.query) + CallbackHandler.code = qs.get("code", [None])[0] + self.send_response(200) + self.send_header("Content-type", "text/html; charset=utf-8") + self.end_headers() + if CallbackHandler.code: + body = "

授权成功,正在保存 token 并发布视频...

" + else: + body = "

未收到 code,请关闭此页。

" + self.wfile.write(body.encode("utf-8")) + + def log_message(self, format, *args): + pass + + +def main(): + key, secret = get_client_creds() + if not key or not secret: + print("未配置 DOUYIN_CLIENT_KEY / DOUYIN_CLIENT_SECRET,无法执行 OAuth。", file=sys.stderr) + print("可在脚本目录创建 .env 或设置环境变量后重试。", file=sys.stderr) + sys.exit(1) + + auth_url = ( + "https://open.douyin.com/platform/oauth/connect/" + f"?client_key={key}&response_type=code&scope=user_info,video.create" + f"&redirect_uri={requests.utils.quote(REDIRECT_URI)}&state=1" + ) + + server = HTTPServer(("127.0.0.1", REDIRECT_PORT), CallbackHandler) + thread = threading.Thread(target=server.handle_request) + thread.start() + webbrowser.open(auth_url) + thread.join(timeout=120) + server.server_close() + + code = CallbackHandler.code + if not code: + print("未获取到授权码,请重试。", file=sys.stderr) + sys.exit(1) + + r = requests.post( + TOKEN_URL, + data={ + "client_key": key, + "client_secret": secret, + "code": code, + "grant_type": "authorization_code", + }, + timeout=10, + ) + data = r.json() + if data.get("data") is None: + print("换 token 失败:", data, file=sys.stderr) + sys.exit(1) + d = data["data"] + token_file = SCRIPT_DIR / "tokens.json" + with open(token_file, "w", encoding="utf-8") as f: + json.dump({"access_token": d["access_token"], "open_id": d["open_id"]}, f, ensure_ascii=False, indent=2) + print("已保存 token 到 tokens.json,开始发布...") + subprocess.run([sys.executable, str(SCRIPT_DIR / "batch_publish_119.py")], check=True) + + +if __name__ == "__main__": + main() diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_pure_api.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_pure_api.py new file mode 100644 index 00000000..29c4bcde --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_pure_api.py @@ -0,0 +1,645 @@ +#!/usr/bin/env python3 +""" +抖音纯 API 视频发布(无浏览器) +基于 CobWeb 思路:Cookie + ec_privateKey + ts_sign → bd-ticket-guard → create_v2 + +流程: + 1. 从 storage_state.json 加载 cookies + localStorage 密钥 + 2. GET /web/api/media/upload/auth/v5/ → VOD 凭证 + 3. GET vod.bytedanceapi.com ApplyUploadInner → 上传地址 + 4. POST 分片上传视频 + 5. POST vod.bytedanceapi.com CommitUploadInner → video_id + 6. POST /web/api/media/aweme/create_v2/ → 发布(bd-ticket-guard 签名) +""" +import asyncio +import base64 +import datetime +import hashlib +import hmac +import json +import os +import random +import string +import sys +import time +import zlib +from pathlib import Path +from urllib.parse import urlencode, quote + +import httpx +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives import serialization +from cryptography import x509 + +SCRIPT_DIR = Path(__file__).parent +COOKIE_FILE = SCRIPT_DIR / "douyin_storage_state.json" +VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片") + +BASE = "https://creator.douyin.com" +AUTH_URL = f"{BASE}/web/api/media/upload/auth/v5/" +VOD_HOST = "https://vod.bytedanceapi.com" +CREATE_V2_URL = f"{BASE}/web/api/media/aweme/create_v2/" +USER_INFO_URL = f"{BASE}/web/api/media/user/info/" +CHUNK_SIZE = 3 * 1024 * 1024 + +UA = ( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" +) + +TITLES = { + "早起不是为了开派对,是不吵老婆睡觉.mp4": + "早起不是为了开派对,是不吵老婆睡觉。初衷就这一个。#Soul派对 #创业日记 #晨间直播 #私域干货", + "懒人的活法 动作简单有利可图正反馈.mp4": + "懒有懒的活法:动作简单、有利可图、正反馈,就能坐得住。#Soul派对 #副业 #私域 #切片变现", + "初期团队先找两个IS,比钱好使 ENFJ链接人,ENTJ指挥.mp4": + "初期团队先找两个IS,比钱好使。ENFJ链接人,ENTJ指挥。#MBTI #创业团队 #Soul派对", + "ICU出来一年多 活着要在互联网上留下东西.mp4": + "ICU出来一年多,活着要在互联网上留下东西。#人生感悟 #创业 #Soul派对 #记录生活", + "MBTI疗愈SOUL 年轻人测MBTI,40到60岁走五行八卦.mp4": + "年轻人测MBTI,40到60岁走五行八卦。#MBTI #Soul派对 #五行 #疗愈", + "Soul业务模型 派对+切片+小程序全链路.mp4": + "Soul业务模型:派对+切片+小程序全链路。#Soul派对 #商业模式 #私域运营 #小程序", + "Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4": + "Soul切片30秒到8分钟,AI半小时能剪10到30个。#AI剪辑 #Soul派对 #切片变现 #效率工具", + "刷牙听业务逻辑 Soul切片变现怎么跑.mp4": + "刷牙听业务逻辑:Soul切片变现怎么跑。#Soul派对 #切片变现 #副业 #商业逻辑", + "国学易经怎么学 两小时七七八八,召唤作者对话.mp4": + "国学易经怎么学?两小时七七八八,召唤作者对话。#国学 #易经 #Soul派对 #学习方法", + "广点通能投Soul了,1000曝光6到10块.mp4": + "广点通能投Soul了,1000曝光6到10块。#Soul派对 #广点通 #流量投放 #私域获客", + "建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4": + "建立信任不是求来的。卖外挂发邮件三个月拿下德国总代。#销售 #信任 #Soul派对 #商业故事", + "核心就两个字 筛选。能开派对坚持7天的人再谈.mp4": + "核心就两个字:筛选。能开派对坚持7天的人再谈。#筛选 #Soul派对 #创业 #坚持", + "睡眠不好?每天放下一件事,做减法.mp4": + "睡眠不好?每天放下一件事,做减法。#睡眠 #减法 #Soul派对 #生活方式", + "这套体系花了170万,但前端几十块就能参与.mp4": + "这套体系花了170万,但前端几十块就能参与。#商业体系 #Soul派对 #私域 #低成本创业", + "金融AI获客体系 后端30人沉淀12年,前端丢手机.mp4": + "金融AI获客体系:后端30人沉淀12年,前端丢手机。#AI获客 #金融 #Soul派对 #商业模式", +} + + +# ═══════════════════════════════════════════════════════════ +# Storage State 加载 +# ═══════════════════════════════════════════════════════════ +class SecurityKeys: + def __init__(self, state_path: Path): + with open(state_path, "r", encoding="utf-8") as f: + state = json.load(f) + + self.cookies = self._extract_cookies(state) + self.cookie_str = "; ".join(f"{k}={v}" for k, v in self.cookies.items()) + self.ms_token = "" + self.ec_private_key = None + self.ec_public_key_bytes = b"" + self.server_public_key = None + self.ticket = "" + self.ts_sign_raw = "" + self.csrf_token = self.cookies.get("passport_csrf_token", "") + + for origin in state.get("origins", []): + if "creator.douyin.com" not in origin.get("origin", ""): + continue + for item in origin.get("localStorage", []): + name, val = item["name"], item["value"] + if "s_sdk_crypt_sdk" in name: + d = json.loads(json.loads(val)["data"]) + pem = d["ec_privateKey"].replace("\\r\\n", "\n") + self.ec_private_key = serialization.load_pem_private_key( + pem.encode(), password=None + ) + pub = self.ec_private_key.public_key() + self.ec_public_key_bytes = pub.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint, + ) + elif "s_sdk_server_cert_key" in name: + cert_pem = json.loads(val)["cert"] + cert = x509.load_pem_x509_certificate(cert_pem.encode()) + self.server_public_key = cert.public_key() + elif "s_sdk_sign_data_key" in name and "web_protect" in name: + d = json.loads(json.loads(val)["data"]) + self.ticket = d["ticket"] + self.ts_sign_raw = d["ts_sign"] + elif name == "xmst": + self.ms_token = val + + @staticmethod + def _extract_cookies(state: dict) -> dict: + result = {} + for c in state.get("cookies", []): + if "douyin.com" in c.get("domain", ""): + result[c["name"]] = c["value"] + return result + + def compute_ticket_guard(self, path: str) -> dict: + """计算 bd-ticket-guard 头 (ECDH + HMAC)""" + if not self.ec_private_key or not self.server_public_key: + return {} + + eph_priv = ec.generate_private_key(ec.SECP256R1()) + eph_pub = eph_priv.public_key() + eph_pub_bytes = eph_pub.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint, + ) + shared_secret = eph_priv.exchange(ec.ECDH(), self.server_public_key) + + ts = int(time.time()) + + ts_hex = self.ts_sign_raw.replace("ts.2.", "") + ts_bytes = bytes.fromhex(ts_hex) + ts_first = ts_bytes[:32] + ts_last = ts_bytes[32:] + new_first = bytes(a ^ b for a, b in zip(ts_first, shared_secret[:32])) + new_ts_sign = "ts.2." + (new_first + ts_last).hex() + + msg = f"{self.ticket},{path},{ts}" + req_sign = hmac.new(shared_secret, msg.encode(), hashlib.sha256).digest() + + client_data = { + "ts_sign": new_ts_sign, + "req_content": "ticket,path,timestamp", + "req_sign": base64.b64encode(req_sign).decode(), + "timestamp": ts, + } + + return { + "bd-ticket-guard-client-data": base64.b64encode( + json.dumps(client_data).encode() + ).decode(), + "bd-ticket-guard-ree-public-key": base64.b64encode(eph_pub_bytes).decode(), + "bd-ticket-guard-version": "2", + "bd-ticket-guard-web-version": "2", + "bd-ticket-guard-web-sign-type": "1", + } + + +# ═══════════════════════════════════════════════════════════ +# AWS4-HMAC-SHA256 签名 +# ═══════════════════════════════════════════════════════════ +def _hmac_sha256(key: bytes, msg: str) -> bytes: + return hmac.new(key, msg.encode(), hashlib.sha256).digest() + + +USER_ID = "95519194897" + + +def aws4_sign(ak: str, sk: str, token: str, qs: str, + method: str = "GET", body: bytes = b"") -> tuple: + now = datetime.datetime.now(datetime.timezone.utc) + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + ds = now.strftime("%Y%m%d") + region, service = "cn-north-1", "vod" + body_hash = hashlib.sha256(body).hexdigest() + + if method == "POST": + signed_headers = "content-type;x-amz-date;x-amz-security-token" + header_str = ( + f"content-type:text/plain;charset=UTF-8\n" + f"x-amz-date:{amz_date}\n" + f"x-amz-security-token:{token}\n" + ) + else: + signed_headers = "x-amz-date;x-amz-security-token" + header_str = ( + f"x-amz-date:{amz_date}\n" + f"x-amz-security-token:{token}\n" + ) + + canonical = f"{method}\n/\n{qs}\n{header_str}\n{signed_headers}\n{body_hash}" + scope = f"{ds}/{region}/{service}/aws4_request" + sts = f"AWS4-HMAC-SHA256\n{amz_date}\n{scope}\n{hashlib.sha256(canonical.encode()).hexdigest()}" + + k = _hmac_sha256(f"AWS4{sk}".encode(), ds) + k = _hmac_sha256(k, region) + k = _hmac_sha256(k, service) + k = _hmac_sha256(k, "aws4_request") + sig = hmac.new(k, sts.encode(), hashlib.sha256).hexdigest() + + auth = f"AWS4-HMAC-SHA256 Credential={ak}/{scope}, SignedHeaders={signed_headers}, Signature={sig}" + return auth, amz_date, token + + +def _rand(n=11): + return "".join(random.choices(string.ascii_lowercase + string.digits, k=n)) + + +# ═══════════════════════════════════════════════════════════ +# Step 1: 获取上传凭证 +# ═══════════════════════════════════════════════════════════ +async def get_upload_auth(client: httpx.AsyncClient, keys: SecurityKeys) -> dict: + print(" [1] 获取上传凭证...") + resp = await client.get( + AUTH_URL, headers={"Cookie": keys.cookie_str, "User-Agent": UA} + ) + resp.raise_for_status() + data = resp.json() + if data.get("status_code") != 0: + raise RuntimeError(f"auth 失败: {data}") + auth = json.loads(data["auth"]) + print(f" AK={auth['AccessKeyID'][:15]}...") + return { + "ak": data["ak"], + "access_key_id": auth["AccessKeyID"], + "secret": auth["SecretAccessKey"], + "token": auth["SessionToken"], + } + + +# ═══════════════════════════════════════════════════════════ +# Step 2: 获取上传地址 +# ═══════════════════════════════════════════════════════════ +async def apply_upload(client: httpx.AsyncClient, auth: dict, file_size: int) -> dict: + print(" [2] 获取上传地址...") + params = { + "Action": "ApplyUploadInner", + "FileSize": str(file_size), + "FileType": "video", + "IsInner": "1", + "SpaceName": "aweme", + "Version": "2020-11-19", + "app_id": "2906", + "s": _rand(), + } + qs = "&".join(f"{k}={v}" for k, v in sorted(params.items())) + authorization, amz_date, token = aws4_sign( + auth["access_key_id"], auth["secret"], auth["token"], qs + ) + url = f"{VOD_HOST}/?{qs}" + resp = await client.get( + url, + headers={ + "authorization": authorization, + "x-amz-date": amz_date, + "x-amz-security-token": token, + "User-Agent": UA, + }, + ) + resp.raise_for_status() + data = resp.json() + result = data.get("Result") or {} + + inner = result.get("InnerUploadAddress") or {} + nodes = inner.get("UploadNodes") or [] + if not nodes: + raise RuntimeError(f"ApplyUploadInner 无 UploadNodes: {data}") + + node = nodes[0] + host = node["UploadHost"] + store = node["StoreInfos"][0] + session_key = node["SessionKey"] + vid = node["Vid"] + + print(f" vid={vid}, host={host}") + return { + "session_key": session_key, + "host": host, + "store_uri": store["StoreUri"], + "auth_token": store["Auth"], + "upload_id": store["UploadID"], + "vid": vid, + } + + +# ═══════════════════════════════════════════════════════════ +# Step 3: 分片上传 +# ═══════════════════════════════════════════════════════════ +async def upload_chunks( + client: httpx.AsyncClient, info: dict, file_path: str +) -> bool: + print(" [3] 上传视频...") + raw = Path(file_path).read_bytes() + total = len(raw) + n_chunks = (total + CHUNK_SIZE - 1) // CHUNK_SIZE + base_url = f"https://{info['host']}/upload/v1/{info['store_uri']}" + upload_id = info["upload_id"] + auth_h = {"Authorization": info["auth_token"], "User-Agent": UA} + + crc_parts = [] + for i in range(n_chunks): + start = i * CHUNK_SIZE + end = min(start + CHUNK_SIZE, total) + chunk = raw[start:end] + crc32_hex = "%08x" % (zlib.crc32(chunk) & 0xFFFFFFFF) + + resp = await client.post( + f"{base_url}?uploadid={upload_id}&part_number={i+1}&phase=transfer", + content=chunk, + headers={**auth_h, "Content-CRC32": crc32_hex, + "Content-Type": "application/octet-stream"}, + timeout=120.0, + ) + resp_data = resp.json() if resp.status_code == 200 else {} + if resp_data.get("code") != 2000: + print(f" chunk {i+1}/{n_chunks} 失败: {resp.text[:200]}") + return False + sv_crc = resp_data.get("data", {}).get("crc32", crc32_hex) + crc_parts.append(f"{i+1}:{sv_crc}") + print(f" chunk {i+1}/{n_chunks} ok (crc32={sv_crc})") + + finish_body = "\n".join(crc_parts).encode() + finish_resp = await client.post( + f"{base_url}?uploadid={upload_id}&phase=finish", + content=finish_body, + headers={**auth_h, "Content-Type": "text/plain"}, + timeout=60.0, + ) + fd = finish_resp.json() if finish_resp.status_code == 200 else {} + if fd.get("code") == 2000: + print(f" finish ok") + return True + print(f" finish: {fd}") + return False + + +# ═══════════════════════════════════════════════════════════ +# Step 4: CommitUploadInner → video_id +# ═══════════════════════════════════════════════════════════ +async def commit_upload( + client: httpx.AsyncClient, auth: dict, session_key: str +) -> str: + print(" [4] CommitUploadInner (POST)...") + qs_params = { + "Action": "CommitUploadInner", + "SpaceName": "aweme", + "Version": "2020-11-19", + "app_id": "2906", + "user_id": USER_ID, + } + qs = "&".join(f"{k}={v}" for k, v in sorted(qs_params.items())) + + body = json.dumps({ + "SessionKey": session_key, + "Functions": [{"Name": "GetMeta"}], + }).encode("utf-8") + + authorization, amz_date, token = aws4_sign( + auth["access_key_id"], auth["secret"], auth["token"], qs, + method="POST", body=body, + ) + url = f"{VOD_HOST}/?{qs}" + resp = await client.post( + url, + content=body, + headers={ + "authorization": authorization, + "x-amz-date": amz_date, + "x-amz-security-token": token, + "content-type": "text/plain;charset=UTF-8", + "User-Agent": UA, + }, + timeout=30.0, + ) + resp.raise_for_status() + data = resp.json() + err = data.get("ResponseMetadata", {}).get("Error", {}) + if err.get("CodeN", 0): + print(f" CommitUpload 失败: {err}") + return "" + results = data.get("Result", {}).get("Results", []) + if results: + vid = results[0].get("Vid", "") + if vid: + print(f" video_id={vid}") + return vid + print(f" CommitUpload 响应: {json.dumps(data, ensure_ascii=False)[:300]}") + return "" + + +# ═══════════════════════════════════════════════════════════ +# Step 5: 等待视频就绪 (轮询 transend) +# ═══════════════════════════════════════════════════════════ +async def wait_video_ready( + client: httpx.AsyncClient, keys: SecurityKeys, video_id: str, + max_wait: int = 15, +) -> bool: + print(" [5] 等待转码...") + url = f"{BASE}/web/api/media/video/transend/" + for i in range(max_wait): + try: + resp = await client.get( + url, + params={"video_id": video_id, "cookie_enabled": "true", "aid": "2906"}, + headers={"Cookie": keys.cookie_str, "User-Agent": UA, + "Referer": "https://creator.douyin.com/creator-micro/content/post/video"}, + timeout=10.0, + ) + data = resp.json() + if data.get("encode") == 1: + print(f" 转码完成 ({data.get('duration', 0):.1f}s)") + return True + except Exception: + pass + await asyncio.sleep(2) + print(" 转码未完成,继续发布(服务端会后台处理)") + return True + + +# ═══════════════════════════════════════════════════════════ +# Step 6: 发布 create_v2 +# ═══════════════════════════════════════════════════════════ +async def create_v2( + client: httpx.AsyncClient, + keys: SecurityKeys, + video_id: str, + title: str, + timing_ts: int = 0, +) -> dict: + print(" [6] 发布 create_v2...") + path = "/web/api/media/aweme/create_v2/" + creation_id = f"{_rand(8)}{int(time.time() * 1000)}" + + body = { + "item": { + "common": { + "text": title, + "caption": title, + "item_title": "", + "activity": "[]", + "text_extra": "[]", + "challenges": "[]", + "mentions": "[]", + "hashtag_source": "", + "hot_sentence": "", + "interaction_stickers": "[]", + "visibility_type": 0, + "download": 1, + "timing": timing_ts if timing_ts > 0 else 0, + "creation_id": creation_id, + "media_type": 4, + "video_id": video_id, + "music_source": 0, + "music_id": None, + }, + "cover": { + "custom_cover_image_height": 0, + "custom_cover_image_width": 0, + "poster": "", + "poster_delay": 0, + }, + } + } + + guard_headers = keys.compute_ticket_guard(path) + + query_params = { + "read_aid": "2906", + "cookie_enabled": "true", + "screen_width": "1280", + "screen_height": "720", + "browser_language": "zh-CN", + "browser_platform": "MacIntel", + "browser_name": "Mozilla", + "browser_version": UA.split("Chrome/")[1].split(" ")[0] if "Chrome/" in UA else "143.0.0.0", + "browser_online": "true", + "timezone_name": "Asia/Shanghai", + "aid": "1128", + "support_h265": "1", + } + if keys.ms_token: + query_params["msToken"] = keys.ms_token + + headers = { + "Cookie": keys.cookie_str, + "User-Agent": UA, + "Content-Type": "application/json", + "Accept": "application/json, text/plain, */*", + "Referer": "https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page", + "Origin": "https://creator.douyin.com", + } + if keys.csrf_token: + headers["x-secsdk-csrf-token"] = f"000100000001{keys.csrf_token[:32]}" + headers.update(guard_headers) + + url = CREATE_V2_URL + "?" + urlencode(query_params) + + resp = await client.post( + url, headers=headers, json=body, timeout=30.0, + ) + + print(f" HTTP {resp.status_code}") + print(f" Headers: {dict(resp.headers)}") + + raw = resp.text + if not raw: + print(" 空响应(签名被拒绝或安全限制)") + return {"status_code": -1, "error": "empty_response"} + + print(f" 响应: {raw[:500]}") + try: + return resp.json() + except Exception: + return {"status_code": -1, "error": raw[:200]} + + +# ═══════════════════════════════════════════════════════════ +# 单视频发布 +# ═══════════════════════════════════════════════════════════ +async def publish_one( + keys: SecurityKeys, + video_path: str, + title: str, + timing_ts: int = 0, + idx: int = 1, + total: int = 1, +) -> bool: + fname = Path(video_path).name + fsize = Path(video_path).stat().st_size + timing_str = datetime.datetime.fromtimestamp(timing_ts).strftime("%m-%d %H:%M") if timing_ts > 0 else "立即" + + print(f"\n{'='*60}") + print(f" [{idx}/{total}] {fname}") + print(f" 大小: {fsize/1024/1024:.1f}MB | 定时: {timing_str}") + print(f" 标题: {title[:60]}") + print(f"{'='*60}") + + async with httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client: + auth = await get_upload_auth(client, keys) + info = await apply_upload(client, auth, fsize) + if not await upload_chunks(client, info, video_path): + print(" [✗] 上传失败") + return False + video_id = await commit_upload(client, auth, info["session_key"]) + if not video_id: + print(" [✗] 未获取到 video_id") + return False + await wait_video_ready(client, keys, video_id) + result = await create_v2(client, keys, video_id, title, timing_ts) + + if result.get("status_code") == 0: + print(" [✓] 发布成功!") + return True + else: + print(f" [✗] 发布失败: {result}") + return False + + +# ═══════════════════════════════════════════════════════════ +# 主流程 +# ═══════════════════════════════════════════════════════════ +async def main(): + if not COOKIE_FILE.exists(): + print("[✗] Cookie 不存在,请先运行 douyin_login.py") + return 1 + + keys = SecurityKeys(COOKIE_FILE) + print(f"[✓] Cookie 加载 ({len(keys.cookies)} items)") + print(f" msToken: {'✓' if keys.ms_token else '✗'}") + print(f" ec_privateKey: {'✓' if keys.ec_private_key else '✗'}") + print(f" server_public_key: {'✓' if keys.server_public_key else '✗'}") + print(f" ticket: {'✓' if keys.ticket else '✗'}") + print(f" ts_sign: {'✓' if keys.ts_sign_raw else '✗'}") + print(f" csrf_token: {'✓' if keys.csrf_token else '✗'}") + + async with httpx.AsyncClient(timeout=15.0) as c: + resp = await c.get( + USER_INFO_URL, headers={"Cookie": keys.cookie_str, "User-Agent": UA} + ) + data = resp.json() + if data.get("status_code") != 0: + print(f"[✗] Cookie 无效: {data}") + return 1 + print(f"[✓] 用户: {data.get('user_info', {}).get('nickname', 'unknown')}\n") + + videos = sorted(VIDEO_DIR.glob("*.mp4")) + if not videos: + print("[✗] 未找到视频") + return 1 + print(f"[i] 共 {len(videos)} 条视频") + + now_ts = int(time.time()) + base_ts = ((now_ts + 3600) // 3600 + 1) * 3600 + + schedule = [] + for i, vp in enumerate(videos): + ts = base_ts + i * 3600 + title = TITLES.get(vp.name, f"{vp.stem} #Soul派对 #创业日记") + schedule.append((vp, title, ts)) + dt_str = datetime.datetime.fromtimestamp(ts).strftime("%m-%d %H:%M") + print(f" {i+1:2d}. {dt_str} | {vp.name[:50]}") + + results = [] + for i, (vp, title, ts) in enumerate(schedule): + ok = await publish_one(keys, str(vp), title, ts, i + 1, len(schedule)) + results.append((vp.name, ok, ts)) + if i < len(schedule) - 1 and ok: + print(" 等待 5s...") + await asyncio.sleep(5) + + print(f"\n{'='*60}") + print(" 发布汇总") + print(f"{'='*60}") + for name, ok, ts in results: + s = "✓" if ok else "✗" + t = datetime.datetime.fromtimestamp(ts).strftime("%m-%d %H:%M") + print(f" [{s}] {t} | {name}") + success = sum(1 for _, ok, _ in results if ok) + print(f"\n 成功: {success}/{len(results)}") + return 0 if success == len(results) else 1 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_pw_publish.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_pw_publish.py new file mode 100644 index 00000000..13004c2c --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_pw_publish.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python3 +""" +抖音视频批量发布 - Playwright 方案 (基于万推架构) +流程: 打开创作者中心 → set_input_files 上传 → 填写标题/话题 → 定时发布 → 保存Cookie +""" +import asyncio +import os +import sys +import time +from datetime import datetime +from pathlib import Path + +from playwright.async_api import async_playwright + +SCRIPT_DIR = Path(__file__).parent +COOKIE_FILE = SCRIPT_DIR / "douyin_storage_state.json" +STEALTH_JS = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend/utils/stealth.min.js") +VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片") + +CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" +HEADLESS = False + +TITLES = { + "早起不是为了开派对,是不吵老婆睡觉.mp4": + "早起不是为了开派对,是不吵老婆睡觉。#Soul派对 #创业日记 #晨间直播 #私域干货", + "懒人的活法 动作简单有利可图正反馈.mp4": + "懒有懒的活法:动作简单、有利可图、正反馈。#Soul派对 #副业 #私域 #切片变现", + "初期团队先找两个IS,比钱好使 ENFJ链接人,ENTJ指挥.mp4": + "初期团队先找两个IS,比钱好使。ENFJ链接人,ENTJ指挥。#MBTI #创业团队 #Soul派对", + "ICU出来一年多 活着要在互联网上留下东西.mp4": + "ICU出来一年多,活着要在互联网上留下东西。#人生感悟 #创业 #Soul派对 #记录生活", + "MBTI疗愈SOUL 年轻人测MBTI,40到60岁走五行八卦.mp4": + "年轻人测MBTI,40到60岁走五行八卦。#MBTI #Soul派对 #五行 #疗愈", + "Soul业务模型 派对+切片+小程序全链路.mp4": + "Soul业务模型:派对+切片+小程序全链路。#Soul派对 #商业模式 #私域运营 #小程序", + "Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4": + "Soul切片30秒到8分钟,AI半小时能剪10到30个。#AI剪辑 #Soul派对 #切片变现", + "刷牙听业务逻辑 Soul切片变现怎么跑.mp4": + "刷牙听业务逻辑:Soul切片变现怎么跑。#Soul派对 #切片变现 #副业 #商业逻辑", + "国学易经怎么学 两小时七七八八,召唤作者对话.mp4": + "国学易经怎么学?两小时七七八八,召唤作者对话。#国学 #易经 #Soul派对", + "广点通能投Soul了,1000曝光6到10块.mp4": + "广点通能投Soul了,1000曝光6到10块。#Soul派对 #广点通 #流量投放 #私域获客", + "建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4": + "建立信任不是求来的。卖外挂发邮件三个月拿下德国总代。#销售 #信任 #Soul派对", + "核心就两个字 筛选。能开派对坚持7天的人再谈.mp4": + "核心就两个字:筛选。能开派对坚持7天的人再谈。#筛选 #Soul派对 #创业", + "睡眠不好?每天放下一件事,做减法.mp4": + "睡眠不好?每天放下一件事,做减法。#睡眠 #减法 #Soul派对 #生活方式", + "这套体系花了170万,但前端几十块就能参与.mp4": + "这套体系花了170万,但前端几十块就能参与。#商业体系 #Soul派对 #私域", + "金融AI获客体系 后端30人沉淀12年,前端丢手机.mp4": + "金融AI获客体系:后端30人沉淀12年,前端丢手机。#AI获客 #金融 #Soul派对", +} + + +def get_title(filename: str) -> str: + return TITLES.get(filename, f"{Path(filename).stem} #Soul派对 #创业日记 #卡若创业派对") + + +async def publish_one(video_path: str, title: str, publish_date: datetime | None, idx: int, total: int) -> bool: + """上传并发布单条视频""" + print(f"\n{'='*60}") + print(f" [{idx+1}/{total}] {Path(video_path).name}") + print(f" 标题: {title[:50]}") + if publish_date: + print(f" 定时: {publish_date.strftime('%Y-%m-%d %H:%M')}") + print(f"{'='*60}") + + async with async_playwright() as pw: + browser = await pw.chromium.launch( + headless=HEADLESS, + executable_path=CHROME_PATH if os.path.exists(CHROME_PATH) else None, + ) + context = await browser.new_context(storage_state=str(COOKIE_FILE)) + if STEALTH_JS.exists(): + await context.add_init_script(path=str(STEALTH_JS)) + + page = await context.new_page() + + try: + # 1. 打开上传页 + print(" [1] 打开上传页...") + await page.goto( + "https://creator.douyin.com/creator-micro/content/upload", + wait_until="domcontentloaded", timeout=60000, + ) + await page.wait_for_url("**/creator.douyin.com/**/upload**", timeout=60000) + await page.wait_for_load_state("load", timeout=20000) + + # 检查登录状态 + if await page.get_by_text("手机号登录").count() or await page.get_by_text("扫码登录").count(): + print(" [!] Cookie 失效,需重新登录") + return False + + try: + await page.get_by_text("上传视频", exact=False).first.wait_for(state="visible", timeout=15000) + except Exception: + pass + await asyncio.sleep(3) + + # 2. 上传文件 + print(" [2] 上传视频文件...") + upload_selectors = [ + "input[type='file']", + "div[class^='container'] input", + "[class*='upload'] input[type='file']", + "[class^='upload-btn-input']", + ] + uploaded = False + for sel in upload_selectors: + try: + loc = page.locator(sel).first + await loc.wait_for(state="attached", timeout=5000) + await loc.set_input_files(video_path, timeout=60000) + print(f" 上传成功 (选择器: {sel})") + uploaded = True + break + except Exception: + continue + if not uploaded: + print(" [!] 未找到上传 input") + return False + + # 3. 等待页面跳转到发布页 + print(" [3] 等待进入发布页...") + for _ in range(120): + try: + await page.wait_for_url( + "https://creator.douyin.com/creator-micro/content/publish*", + timeout=2000, + ) + print(" 进入发布页 (v1)") + break + except Exception: + try: + await page.wait_for_url( + "https://creator.douyin.com/creator-micro/content/post/video*", + timeout=2000, + ) + print(" 进入发布页 (v2)") + break + except Exception: + pass + else: + print(" [!] 等待发布页超时") + ss = SCRIPT_DIR / f"timeout_nav_{idx}.png" + await page.screenshot(path=str(ss), full_page=True) + return False + + await asyncio.sleep(3) + + # 4. 填写标题 + print(" [4] 填写标题...") + title_text = title[:30] + title_filled = False + + # 方式A: 万推的方式 - 通过"作品标题"文本定位 + try: + tc = page.get_by_text("作品标题").locator("..").locator("xpath=following-sibling::div[1]").locator("input") + if await tc.count(): + await tc.fill(title_text) + title_filled = True + print(" 标题填写成功 (方式A)") + except Exception: + pass + + # 方式B: .notranslate 区域 + if not title_filled: + try: + tc = page.locator(".notranslate").first + await tc.click(timeout=5000) + await page.keyboard.press("Meta+KeyA") + await page.keyboard.press("Delete") + await page.keyboard.type(title_text, delay=20) + await page.keyboard.press("Enter") + title_filled = True + print(" 标题填写成功 (方式B)") + except Exception: + pass + + # 方式C: 直接 JS 注入 + if not title_filled: + try: + await page.evaluate(f'''() => {{ + const inp = document.querySelector('input[placeholder*="标题"]') || document.querySelector('input.semi-input'); + if (inp) {{ inp.value = {repr(title_text)}; inp.dispatchEvent(new Event('input', {{bubbles:true}})); }} + }}''') + title_filled = True + print(" 标题填写成功 (方式C: JS注入)") + except Exception as e: + print(f" [!] 标题填写全部失败: {e}") + + # 5. 填写话题 + print(" [5] 填写话题...") + tags = [t.strip() for t in title.split("#") if t.strip() and len(t.strip()) < 20] + if tags: + tags = tags[1:] # 第一个通常是标题文本 + try: + zone = page.locator(".zone-container") + for tag in tags[:5]: + await zone.type(f"#{tag} ", delay=30) + await asyncio.sleep(0.3) + # 关闭话题下拉 + await page.keyboard.press("Escape") + await asyncio.sleep(0.3) + await page.mouse.click(10, 10) + print(f" 已添加 {min(len(tags), 5)} 个话题") + except Exception as e: + print(f" 话题填写跳过: {e}") + + # 6. 等待视频上传完成 + print(" [6] 等待视频上传完成...") + for _ in range(120): + try: + n = await page.locator('[class^="long-card"] div:has-text("重新上传")').count() + if n > 0: + print(" 视频上传完毕") + break + except Exception: + pass + await asyncio.sleep(2) + else: + print(" [!] 视频上传等待超时,继续尝试发布...") + + # 7. 定时发布 + if publish_date: + print(f" [7] 设置定时发布: {publish_date.strftime('%Y-%m-%d %H:%M')}...") + try: + label = page.locator("[class^='radio']:has-text('定时发布')") + await label.click(timeout=5000) + await asyncio.sleep(1) + date_str = publish_date.strftime("%Y-%m-%d %H:%M") + date_input = page.locator('.semi-input[placeholder="日期和时间"]') + await date_input.click(timeout=5000) + await page.keyboard.press("Meta+KeyA") + await page.keyboard.type(date_str) + await page.keyboard.press("Enter") + await asyncio.sleep(1) + print(f" 定时已设置") + except Exception as e: + print(f" [!] 定时设置失败: {e}") + + # 8. 点击发布 + print(" [8] 点击发布...") + published = False + for attempt in range(30): + try: + pub_btn = page.get_by_role("button", name="发布", exact=True) + if await pub_btn.count(): + await pub_btn.click() + await page.wait_for_url( + "https://creator.douyin.com/creator-micro/content/manage**", + timeout=5000, + ) + print(" [OK] 视频发布成功!") + published = True + break + except Exception: + # 处理封面弹窗 + try: + if await page.get_by_text("请设置封面后再发布").first.is_visible(): + print(" 需要封面,自动选择...") + cover = page.locator('[class^="recommendCover-"]').first + if await cover.count(): + await cover.click() + await asyncio.sleep(1) + if await page.get_by_text("是否确认应用此封面?").first.is_visible(): + await page.get_by_role("button", name="确定").click() + await asyncio.sleep(1) + except Exception: + pass + await asyncio.sleep(2) + + if not published: + print(" [!] 发布超时") + ss = SCRIPT_DIR / f"timeout_publish_{idx}.png" + await page.screenshot(path=str(ss), full_page=True) + + # 9. 保存 Cookie + await context.storage_state(path=str(COOKIE_FILE)) + print(" Cookie 已更新") + + return published + + except Exception as e: + print(f" [!] 异常: {e}") + ss = SCRIPT_DIR / f"error_{idx}.png" + try: + await page.screenshot(path=str(ss), full_page=True) + except Exception: + pass + return False + finally: + await context.close() + await browser.close() + + +async def main(): + if not COOKIE_FILE.exists(): + print("[!] Cookie 不存在,请先运行 douyin_login.py") + return 1 + + videos = sorted(VIDEO_DIR.glob("*.mp4")) + if not videos: + print("[!] 未找到视频") + return 1 + print(f"[i] 共 {len(videos)} 条视频待发布\n") + + # 定时规划: 从当前+2h开始,每小时一条 + now_ts = int(time.time()) + base_ts = ((now_ts + 2 * 3600) // 3600 + 1) * 3600 + + results = [] + for i, vp in enumerate(videos): + title = get_title(vp.name) + schedule_time = datetime.fromtimestamp(base_ts + i * 3600) + + ok = await publish_one( + video_path=str(vp), + title=title, + publish_date=schedule_time, + idx=i, + total=len(videos), + ) + results.append((vp.name, ok, schedule_time)) + + if i < len(videos) - 1: + wait = 10 if ok else 5 + print(f" 等待 {wait} 秒后继续...") + await asyncio.sleep(wait) + + # 汇总 + print(f"\n{'='*60}") + print(" 发布汇总") + print(f"{'='*60}") + for name, ok, t in results: + s = "OK" if ok else "FAIL" + print(f" [{s:4s}] {t.strftime('%m-%d %H:%M')} | {name}") + success = sum(1 for _, ok, _ in results if ok) + print(f"\n 成功: {success}/{len(results)}") + return 0 if success == len(results) else 1 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_storage_state.json b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_storage_state.json new file mode 100644 index 00000000..f24f196b --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_storage_state.json @@ -0,0 +1 @@ +{"cookies": [{"name": "gd_random", "value": "eyJtYXRjaCI6dHJ1ZSwicGVyY2VudCI6MC4xMjIwNDMwOTA3MDYzODE5N30=.YNa7JmMdnYys8TlLQV5lohdC3WVMmSvYHIheCi8eRXU=", "domain": "creator.douyin.com", "path": "/goofy/douyin_creator_pc/creator_pc_vmok_common/vmok-manifest.json", "expires": 1773670017.915209, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "gd_random", "value": "eyJwZXJjZW50IjowLjEyMjA0MzA5MDcwNjM4MTk3LCJtYXRjaCI6dHJ1ZX0=.GPWMPs56HwezWo47qpWoiYeiKPxYLSOQSKrB0v43v44=", "domain": "creator.douyin.com", "path": "/goofy/douyin_creator_pc/vmok/author-tag/vmok-manifest.json", "expires": 1773670018.771314, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "gd_random", "value": "eyJtYXRjaCI6ZmFsc2UsInBlcmNlbnQiOjAuMTIyMDQzMDkwNzA2MzgxOTd9.047c9kKsZhKT+1LIHvSdAL/+tL4K7ump/iZYUkIlhkI=", "domain": "creator.douyin.com", "path": "/goofy/douyin_creator_pc/mono/creator_content", "expires": 1773670027.762048, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "gd_random", "value": "eyJtYXRjaCI6ZmFsc2UsInBlcmNlbnQiOjAuMTIyMDQzMDkwNzA2MzgxOTd9.047c9kKsZhKT+1LIHvSdAL/+tL4K7ump/iZYUkIlhkI=", "domain": "creator.douyin.com", "path": "/goofy/douyin_creator_pc", "expires": 1773670028.003353, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "gd_random", "value": "eyJtYXRjaCI6ZmFsc2UsInBlcmNlbnQiOjAuMTIyMDQzMDkwNzA2MzgxOTd9.047c9kKsZhKT+1LIHvSdAL/+tL4K7ump/iZYUkIlhkI=", "domain": "creator.douyin.com", "path": "/", "expires": 1773670017.432087, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "x-web-secsdk-uid", "value": "261724d8-318a-468a-9c2f-8cfc266170e5", "domain": "creator.douyin.com", "path": "/", "expires": -1, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "gfkadpd", "value": "2906,33638", "domain": "creator.douyin.com", "path": "/", "expires": 1773324360, "httpOnly": false, "secure": true, "sameSite": "None"}, {"name": "_tea_utm_cache_2906", "value": "undefined", "domain": ".creator.douyin.com", "path": "/", "expires": 1773669961, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "csrf_session_id", "value": "0fed46187ebf52b557b0b930f741ad2c", "domain": "creator.douyin.com", "path": "/", "expires": -1, "httpOnly": false, "secure": true, "sameSite": "None"}, {"name": "bd_ticket_guard_client_web_domain", "value": "2", "domain": ".douyin.com", "path": "/", "expires": 1778249218.100543, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "ttwid", "value": "1%7CzOegw0uJo5zZe6EHeNZLqIM0bYaPmc9CPRFqGTiW4NI%7C1773065162%7C9bcab85427475fb50ee0da6fe75e85de056bfe7f663eeb74a83d67125972d3e5", "domain": ".bytedance.com", "path": "/", "expires": 1804601162.351321, "httpOnly": true, "secure": true, "sameSite": "None"}, {"name": "passport_csrf_token", "value": "64063381d8c27226d45f64646172d892", "domain": ".douyin.com", "path": "/", "expires": 1778249162.383157, "httpOnly": false, "secure": true, "sameSite": "None"}, {"name": "passport_csrf_token_default", "value": "64063381d8c27226d45f64646172d892", "domain": ".douyin.com", "path": "/", "expires": 1778249162.38319, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "sdk_source_info", "value": "7e276470716a68645a606960273f276364697660272927676c715a6d6069756077273f276364697660272927666d776a68605a607d71606b766c6a6b5a7666776c7571273f275e58272927666a6b766a69605a696c6061273f27636469766027292762696a6764695a7364776c6467696076273f275e5827292771273f27353334313234303335363232342778", "domain": ".douyin.com", "path": "/", "expires": 1773065474.190879, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "bit_env", "value": "2oiM7CQFXxQjsmN65go1psrQVvOk0DtBfvrHFs1Wyas2yU_7sQcqVUXKvInxC6XPTwSoUlLoa10SfluZ-L90g_16ylyHQTvoeT5qVAmjD7AibXue4uX3tUjmYZKdukdwyUMylynratnPX22dUSvyKN34fGQ8lv6M85blAXf7OiXRdSn4Bq7iX-8ROExe-EECTRFz5bVZqGa0u6dD3nzw8xrON5jkCU_iPwoF1Hk-5htLKBjAvKWdTIUf3gcwrNL_tJHQl7oIrnsCbKXCiKSxgDafGXUwfulqfFww_oxznFazgBQwGNEh3EYREM7xc3H9Qxp-SSJCacBwdakq0qEzzJBE4qywS6aVNWndSraKFTCWLYkocNRGXMx11mHFdpZ9WyALrrKeLBBnIZGrFFIXMDkmISGt738KQbe3784q8gyUh-_OJJ_bj9lC84F-yUec43W5Co-0vIXWVDVvmbtGgV1KloQzrfwwnZ-ZTKyvkVbpyXqwobhIeCLW-2rb3dDuuANXx1JZM5qJzUXOjWbhPY4oiiqr_CVpIj2x4nfhATA%3D", "domain": ".douyin.com", "path": "/", "expires": 1773065474.190978, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "gulu_source_res", "value": "eyJwX2luIjoiNGQyZWY5YTQ5ZWRjMWRkODFjNjhhNDYzMTkwZDk5YzJlMTJhY2U4OTdjODg1Yzc5M2YzYTE0ODE0ZDQ1NGJkNSJ9", "domain": ".douyin.com", "path": "/", "expires": 1773065474.191006, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "passport_auth_mix_state", "value": "sbhfxf8lyunbpf2ksqlfjbioe2vr6kkk", "domain": ".douyin.com", "path": "/", "expires": 1773065414.191036, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "passport_mfa_token", "value": "CjUAZJLqwjErK%2FOp1fj1DEjWoM1nTopnlVO71rj0vLYsmTPSrSSXXatczE%2B3zW%2Bf08pnHUgKJxpKCjwAAAAAAAAAAAAAUCm%2Fk1w2yDO17qaTuDJLdQZbS0j3kbpAtSiN2Ai%2BAuGDBBqx7sARygs8tWq9iD7UfzIQrtKLDhj2sdFsIAIiAQOnZxdi", "domain": ".douyin.com", "path": "/", "expires": 1778249216.856199, "httpOnly": true, "secure": true, "sameSite": "Lax"}, {"name": "d_ticket", "value": "811717257641ad29fbcab798fb6ae2d0c296c", "domain": ".douyin.com", "path": "/", "expires": 1804601216.856238, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "biz_trace_id", "value": "446714c9", "domain": ".douyin.com", "path": "/", "expires": -1, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "odin_tt", "value": "c24decbc374a321d0b467ce2c586b8c07a9102f3928c0b94b402951be7d45a922b53e96367d937752ecfae5ec6690da2", "domain": ".douyin.com", "path": "/", "expires": 1804601217.287974, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "passport_assist_user", "value": "CjzS9f6l8Ov59xbEw98rTPQVDfh_e1S9Yyo6UL1odQztZz0Uwmw8IoWJYqfhtW2vo39_zHhz6spE7pUUOWkaSgo8AAAAAAAAAAAAAFApNKm_uePs4rUqvWqP9f6CCWCAV30CZ9yI7jKc732ca5xgSRkDKFG6MrndTOgNN-kcEJnRiw4Yia_WVCABIgEDZS8Y4g%3D%3D", "domain": ".douyin.com", "path": "/", "expires": 1807625217.288015, "httpOnly": false, "secure": true, "sameSite": "Lax"}, {"name": "n_mh", "value": "KJdEUPavjSIo_936-mr2L_7xbccTWtvmWNdw6rL7RJM", "domain": ".douyin.com", "path": "/", "expires": 1783433217.288031, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "sid_guard", "value": "e89cf0aa27fff85cf8a66274fba6ef1c%7C1773065217%7C5184000%7CFri%2C+08-May-2026+14%3A06%3A57+GMT", "domain": ".douyin.com", "path": "/", "expires": 1804169217.288041, "httpOnly": true, "secure": true, "sameSite": "Lax"}, {"name": "uid_tt", "value": "1944cc413bc83a73f5e631dfcafe5973", "domain": ".douyin.com", "path": "/", "expires": 1778249217.288054, "httpOnly": true, "secure": true, "sameSite": "Lax"}, {"name": "uid_tt_ss", "value": "1944cc413bc83a73f5e631dfcafe5973", "domain": ".douyin.com", "path": "/", "expires": 1778249217.288064, "httpOnly": true, "secure": true, "sameSite": "None"}, {"name": "sid_tt", "value": "e89cf0aa27fff85cf8a66274fba6ef1c", "domain": ".douyin.com", "path": "/", "expires": 1778249217.288074, "httpOnly": true, "secure": true, "sameSite": "Lax"}, {"name": "sessionid", "value": "e89cf0aa27fff85cf8a66274fba6ef1c", "domain": ".douyin.com", "path": "/", "expires": 1778249217.288083, "httpOnly": true, "secure": true, "sameSite": "Lax"}, {"name": "sessionid_ss", "value": "e89cf0aa27fff85cf8a66274fba6ef1c", "domain": ".douyin.com", "path": "/", "expires": 1778249217.288093, "httpOnly": true, "secure": true, "sameSite": "None"}, {"name": "session_tlb_tag", "value": "sttt%7C8%7C6Jzwqif_-Fz4pmJ0-6bvHP_________ShlaRtrSGyH_--zIdTzr0CF4D8GC2WWcvJ1luxPSSBQo%3D", "domain": ".douyin.com", "path": "/", "expires": 1778249217.288102, "httpOnly": true, "secure": true, "sameSite": "None"}, {"name": "is_staff_user", "value": "false", "domain": ".douyin.com", "path": "/", "expires": 1778249217.288111, "httpOnly": true, "secure": true, "sameSite": "Lax"}, {"name": "sid_ucp_v1", "value": "1.0.0-KGE2ODliMDI5ZjA0NDAxZjIxYzMyM2U2NjhmZTRlYjMzMjg3MTcyYTMKHwiR_ozr4wIQgai7zQYY2hYgDDDP4q_VBTgHQPQHSAQaAmxmIiBlODljZjBhYTI3ZmZmODVjZjhhNjYyNzRmYmE2ZWYxYw", "domain": ".douyin.com", "path": "/", "expires": 1778249217.288124, "httpOnly": true, "secure": true, "sameSite": "Lax"}, {"name": "ssid_ucp_v1", "value": "1.0.0-KGE2ODliMDI5ZjA0NDAxZjIxYzMyM2U2NjhmZTRlYjMzMjg3MTcyYTMKHwiR_ozr4wIQgai7zQYY2hYgDDDP4q_VBTgHQPQHSAQaAmxmIiBlODljZjBhYTI3ZmZmODVjZjhhNjYyNzRmYmE2ZWYxYw", "domain": ".douyin.com", "path": "/", "expires": 1778249217.288134, "httpOnly": true, "secure": true, "sameSite": "None"}, {"name": "ttwid", "value": "1%7CzOegw0uJo5zZe6EHeNZLqIM0bYaPmc9CPRFqGTiW4NI%7C1773065217%7Cfd99b32930e2ded3f5c4fe366ca79cd5b1f2882b5a4b9a7647b963470f52f210", "domain": ".douyin.com", "path": "/", "expires": 1804601217.870775, "httpOnly": true, "secure": true, "sameSite": "None"}, {"name": "_bd_ticket_crypt_doamin", "value": "2", "domain": ".douyin.com", "path": "/", "expires": 1778249218.02964, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "_bd_ticket_crypt_cookie", "value": "a1873b6da6f8238cee8ac6d3d33f26dc", "domain": ".douyin.com", "path": "/", "expires": 1778249218.08013, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "__security_mc_1_s_sdk_sign_data_key_web_protect", "value": "8783b4da-48a3-813a", "domain": ".douyin.com", "path": "/", "expires": 1778249218.080631, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "__security_mc_1_s_sdk_cert_key", "value": "6e30e1e8-49ff-ad40", "domain": ".douyin.com", "path": "/", "expires": 1778249218.080676, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "__security_mc_1_s_sdk_crypt_sdk", "value": "a31cb7da-4673-9c2b", "domain": ".douyin.com", "path": "/", "expires": 1778249218.080713, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "__security_server_data_status", "value": "1", "domain": ".douyin.com", "path": "/", "expires": 1778249218.098806, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "bd_ticket_guard_client_data", "value": "eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWl0ZXJhdGlvbi12ZXJzaW9uIjoxLCJiZC10aWNrZXQtZ3VhcmQtcmVlLXB1YmxpYy1rZXkiOiJCSWJPSWt2bTJFcWQzQTNpQXJETFJSLzBVR0R6a2psd2JUTDVIWVNoM1VNTGE1WFVnanJVVGtnQUFZakZtQytFdmlzNERwWFdvVCt4UUhoUlBlSmlNaFU9IiwiYmQtdGlja2V0LWd1YXJkLXdlYi12ZXJzaW9uIjoyfQ%3D%3D", "domain": ".douyin.com", "path": "/", "expires": 1778249218.100523, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "csrf_session_id", "value": "26f221951a6044b304c2c7e906da8d9f", "domain": "summon.bytedance.com", "path": "/", "expires": -1, "httpOnly": false, "secure": true, "sameSite": "None"}, {"name": "fg_uid", "value": "RID20260309220659FD1689365E8DD936B64D", "domain": "api.feelgood.cn", "path": "/", "expires": 1804601219.536394, "httpOnly": false, "secure": true, "sameSite": "None"}, {"name": "passport_fe_beating_status", "value": "true", "domain": ".creator.douyin.com", "path": "/", "expires": -1, "httpOnly": false, "secure": false, "sameSite": "Lax"}], "origins": [{"origin": "https://creator.douyin.com", "localStorage": [{"name": "login_type_from_login", "value": "\"\""}, {"name": "LOGIN_STATUS", "value": "{\"logintype\":\"user\",\"loginapp\":\"douyin\"}"}, {"name": "__msuuid__", "value": "53e4b856-cc9a-45cf-8b2e-b1aa4833a418"}, {"name": "creator_pc_master_guide_guide-cocenter", "value": "\"true\""}, {"name": "CREATOR_LAYOUT_CONFIG", "value": "{\"headerLogo\":{\"douyin\":{\"src\":\"//lf3-static.bytednsdoc.com/obj/eden-cn/lm-yvahlyj-upfbvk/ljhwZthlaukjlkulzlp/pc/icons/logo.png\"},\"huoshan\":{\"src\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_header_logo_h.png~tplv-obj.image\"}},\"hotsoonHelpShow\":false,\"footerLink\":[{\"children\":\"\u8d26\u53f7\u6388\u6743\u534f\u8bae\",\"href\":\"//lf3-beecdn.bytetos.com/obj/ies-fe-bee/bee_prod/biz_181/bee_prod_181_bee_publish_1095.html\"},{\"children\":\"\u7528\u6237\u670d\u52a1\u534f\u8bae\",\"href\":\" //www.douyin.com/agreement/\"},{\"children\":\"\u9690\u79c1\u653f\u7b56\",\"href\":\" //www.douyin.com/privacy/\"},{\"children\":\"\u8d26\u53f7\u627e\u56de\",\"href\":\" //www.douyin.com/recovery_account/\"},{\"children\":\"\u8054\u7cfb\u6211\u4eec\",\"href\":\" //www.douyin.com/aboutus/\"}],\"footerText\":[[\"2025 \u00a9 \u6296\u97f3\",\"[\u4eacICP\u590716016397\u53f7-3](https://beian.miit.gov.cn/)\",\"[\u5317\u4eac\u6296\u97f3\u79d1\u6280\u6709\u9650\u516c\u53f8](https://lf3-static.bytednsdoc.com/obj/eden-cn/lm-yvahlyj-upfbvk/ljhwZthlaukjlkulzlp/\u8425\u4e1a\u6267\u7167.jpg)\",\"[\u4eacB2-20170846](https://lf3-static.bytednsdoc.com/obj/eden-cn/lm-yvahlyj-upfbvk/ljhwZthlaukjlkulzlp/\u589e\u503c\u7535\u4fe1\u4e1a\u52a1\u7ecf\u8425\u8bb8\u53ef\u8bc1.jpg)\"],[\"[\u4e2d\u56fd\u4e92\u8054\u7f51\u4e3e\u62a5\u4e2d\u5fc3](http://www.12377.cn/)\",\"[\u7f51\u7edc\u6587\u5316\u7ecf\u8425\u8bb8\u53ef\u8bc1-\u4eac\u7f51\u6587\u30142025\u30150181-061\u53f7](https://lf3-static.bytednsdoc.com/obj/eden-cn/lm-yvahlyj-upfbvk/ljhwZthlaukjlkulzlp/\u7f51\u7edc\u7ecf\u8425\u8bb8\u53ef\u8bc1.jpg)\",\"\u8fdd\u6cd5\u548c\u4e0d\u826f\u4fe1\u606f\u4e3e\u62a5\uff1a400-140-2108\",\"\u4e3e\u62a5\u90ae\u7bb1\uff1afeedback@douyin.com\"],[\"![pic](//p3.douyinpic.com/aweme-server-static-resource/gongan_d0289dc.png~tplv-obj.image)\",\"[\u4eac\u516c\u7f51\u5b89\u590711000002002046\u53f7](http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11000002002046)\",\"\u5730\u5740\uff1a\u5317\u4eac\u5e02\u6d77\u6dc0\u533a\u5317\u4e09\u73af\u897f\u8def\u753218\u53f7\u96624\u53f7\u697c2\u5c422022\"]],\"permissionKeys\":[{\"itemKey\":\"/authority\",\"text\":\"\u6388\u6743\u7ba1\u7406\",\"show\":true,\"permission\":\"CreatorConferManage\"},{\"itemKey\":\"/content/\",\"text\":\"\u5185\u5bb9\u7ba1\u7406\",\"path\":[\"/live/media/create\",\"/live/media/room\",\"/live/media/list\"],\"show\":true,\"permission\":\"CreatorContentManage\"},{\"itemKey\":\"/following\",\"text\":\"\u4e92\u52a8\u7ba1\u7406\",\"show\":true,\"permission\":\"CreatorInterManage\"},{\"itemKey\":\"/data\",\"text\":\"\u6570\u636e\u7ba1\u7406\",\"path\":[\"/live/media/data\"],\"show\":true,\"permission\":\"CreatorDataManage\"},{\"itemKey\":\"/musician\",\"text\":\"\u97f3\u4e50\u7ba1\u7406\",\"show\":true,\"restrictHotsoon\":true,\"permission\":\"CreatorMusicManage\"},{\"itemKey\":\"/publicity/topic\",\"text\":\"\u5ba3\u53d1\u7ba1\u7406\",\"show\":false,\"restrictHotsoon\":true,\"permission\":\"CreatorContentManage.Challenge\"}],\"headerIcons\":[{\"src\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_header_notification_icon.svg~tplv-obj.image\",\"link\":\"https://creator.douyin.com/message\",\"visible\":{\"has_unread_message\":false,\"is_login_hotsoon\":false}},{\"src\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_header_notification.png~tplv-obj.image\",\"link\":\"https://creator.douyin.com/message\",\"style\":{\"width\":\"24px\",\"height\":\"24px\"},\"badge\":{\"count\":\"unread_message_count\"},\"visible\":{\"has_unread_message\":true,\"is_login_hotsoon\":false}}],\"sidebarFollowerTip\":\"\u4ec5\u5c55\u793a\u6296\u97f3\u7c89\u4e1d\",\"sidebarData\":{\"data\":[{\"name\":\"\u89c6\u9891\u6570\u636e\",\"keyName\":\"CreatorDataManage\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_sidebar_vedio.svg~tplv-obj.image\",\"auth\":true,\"children\":[{\"path\":\"/data/stats/overview\",\"name\":\"\u6570\u636e\u603b\u89c8\",\"keyName\":\"CreatorDataManage.UserOverview\",\"visible\":{\"is_login_hotsoon\":false}},{\"path\":\"/data/stats/video\",\"name\":\"\u4f5c\u54c1\u6570\u636e\",\"keyName\":\"CreatorDataManage.ItemL2\",\"visible\":{\"is_login_hotsoon\":false}},{\"path\":\"/data/stats/follower/portrait\",\"name\":\"\u7c89\u4e1d\u753b\u50cf\",\"keyName\":\"CreatorDataManage.Portrait\",\"visible\":{\"is_login_hotsoon\":false}},{\"path\":\"/data/stats/hotsoon-overview\",\"name\":\"\u6570\u636e\u603b\u89c8\",\"keyName\":\"CreatorDataManage.UserOverview\",\"visible\":{\"is_login_hotsoon\":true}},{\"path\":\"/data/stats/hotsoon-item\",\"name\":\"\u4f5c\u54c1\u6570\u636e\",\"keyName\":\"CreatorDataManage.ItemL2\",\"visible\":{\"is_login_hotsoon\":true}},{\"path\":\"/weekly\",\"name\":\"\u521b\u4f5c\u5468\u62a5\",\"keyName\":\"CreatorDataManage.WeekReport\",\"visible\":{\"is_login_hotsoon\":false}}]},{\"name\":\"\u76f4\u64ad\u6570\u636e\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_sidebar_data_icon.svg~tplv-obj.image\",\"auth\":true,\"children\":[{\"path\":\"/data/live/overview\",\"name\":\"\u6570\u636e\u603b\u89c8\",\"key\":\"CreatorDataManage.Live\"},{\"path\":\"/live/media/data\",\"name\":\"\u6570\u636e\u603b\u89c8\",\"key\":\"CreatorLiveManage.Data\"},{\"path\":\"/data/live/video\",\"name\":\"\u5355\u573a\u6570\u636e\",\"key\":\"CreatorDataManage.Live\"}]},{\"name\":\"\u91cd\u70b9\u5173\u6ce8\",\"keyName\":\"CreatorDataManage\",\"auth\":true,\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_sidebar_inportant.svg~tplv-obj.image\",\"visible\":[{\"status_code\":8,\"window.location.host\":\"creator.douyin.com\"},{\"status_code\":15384,\"window.location.host\":\"creator.douyin.com\"},{\"is_login_hotsoon\":false}],\"children\":[{\"path\":\"/data/important/following\",\"name\":\"\u6211\u5173\u5fc3\u7684\",\"keyName\":\"CreatorDataManage.TraceOthers\"},{\"path\":\"/data/important/keyword\",\"name\":\"\u4e0e\u6211\u76f8\u5173\",\"keyName\":\"CreatorDataManage.SearchSelf\"}]}],\"interaction\":[{\"name\":\"\u4e92\u52a8\u7ba1\u7406\",\"keyName\":\"CreatorInterManage\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_sidebar_following.svg~tplv-obj.image\",\"auth\":true,\"children\":[{\"path\":\"/following/following\",\"name\":\"\u5173\u6ce8\u7ba1\u7406\"},{\"path\":\"/following/follower\",\"name\":\"\u7c89\u4e1d\u7ba1\u7406\"},{\"path\":\"/following/comment\",\"name\":\"\u8bc4\u8bba\u7ba1\u7406\",\"visible\":{\"is_login_hotsoon\":false}},{\"path\":\"/following/chat\",\"name\":\"\u79c1\u4fe1\u7ba1\u7406\",\"key\":\"CreatorInterManage.im\",\"keyName\":\"CreatorInterManage.im\",\"visible\":{\"is_login_hotsoon\":false,\"douyin_user_verify_info.teen_model\":false}}]}],\"publicity\":[{\"name\":\"\u5ba3\u53d1\u7ba1\u7406\",\"key\":\"CreatorContentManage.Challenge\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_sidebar_publicity.svg~tplv-obj.image\",\"children\":[{\"path\":\"/publicity/topic\",\"name\":\"\u8bdd\u9898\u7ba1\u7406\"}]}],\"music\":[{\"name\":\"\u97f3\u4e50\u7ba1\u7406\",\"key\":\"CreatorMusicManage\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_sidebar_music.svg~tplv-obj.image\",\"children\":[{\"path\":\"/musician\",\"name\":\"\u6982\u89c8\"},{\"path\":\"/musician/songs\",\"name\":\"\u97f3\u4e50\u7ba1\u7406\"},{\"path\":\"/musician/statistics\",\"name\":\"\u6570\u636e\u7ba1\u7406\"}]}],\"live\":[{\"name\":\"\u76f4\u64ad\u7ba1\u7406\",\"key\":\"CreatorLiveManage\",\"children\":[{\"path\":\"/live/media/create\",\"name\":\"\u521b\u5efa\u76f4\u64ad\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ttfe_open_creator_sidebar_livecreate.svg~tplv-obj.image\",\"key\":\"CreatorLiveManage.Create\"},{\"path\":\"/live/media/list\",\"name\":\"\u76f4\u64ad\u5217\u8868\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ttfe_open_creator_sidebar_livelist.svg~tplv-obj.image\",\"key\":\"CreatorLiveManage.List\"},{\"path\":\"/live/media/replay\",\"name\":\"\u76f4\u64ad\u56de\u653e\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ttfe_open_creator_sidebar_livereplay.svg~tplv-obj.image\",\"key\":\"CreatorLiveManage.Replay\"}]},{\"name\":\"\u6570\u636e\u7ba1\u7406\",\"key\":\"CreatorLiveManage.Data\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ttfe_open_creator_sidebar_livedata.svg~tplv-obj.image\",\"children\":[{\"path\":\"/live/media/data\",\"name\":\"\u76f4\u64ad\u6570\u636e\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ttfe_open_creator_sidebar_livedata.svg~tplv-obj.image\",\"key\":\"CreatorLiveManage.Data\"}]}],\"media\":[{\"name\":\"\u53d1\u5e03\u89c6\u9891\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_sidebar_upload_v2.svg~tplv-obj.image\",\"path\":[\"/content/upload\",\"/content/publish\"]},{\"name\":\"\u5185\u5bb9\u7ba1\u7406\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_sidebar_manage_v2.svg~tplv-obj.image\",\"children\":[{\"path\":\"/content/manage\",\"name\":\"\u89c6\u9891\u7ba1\u7406\",\"icon\":\"\"},{\"path\":[\"/content/collection/manage\",\"/content/collection/detail\",\"/content/collection/create\"],\"name\":\"\u5408\u96c6\u7ba1\u7406\",\"icon\":\"\",\"visContextKey\":\"userInfo.mix_permission\",\"visContextValue\":true},{\"path\":\"/content/safeguard\",\"name\":\"\u7ef4\u6743\u7ba1\u7406\",\"icon\":\"\",\"visContextKey\":\"userInfo.permission.user_sign\",\"visContextValue\":true}]},{\"name\":\"\u76f4\u64ad\u7ba1\u7406\",\"icon\":\"//p3.douyinpic.com/aweme-server-static-resource/ies_douyin_opencn_tiktok_creator_sidebar_livecreate_v2.svg~tplv-obj.image\",\"visContextKey\":\"creatorMenu.live_manage\",\"visContextValue\":true,\"children\":[{\"path\":\"/live/media/create\",\"name\":\"\u521b\u5efa\u76f4\u64ad\",\"icon\":\"\",\"key\":\"CreatorLiveManage.Create\",\"visContextKey\":\"creatorMenu.media_auth\",\"visContextValue\":true},{\"path\":\"/live/media/list\",\"name\":\"\u76f4\u64ad\u5217\u8868\",\"icon\":\"\",\"key\":\"CreatorLiveManage.List\",\"visContextKey\":\"creatorMenu.media_auth\",\"visContextValue\":true},{\"path\":\"/content/live/replay\",\"name\":\"\u76f4\u64ad\u56de\u653e\",\"icon\":\"\",\"key\":\"CreatorLiveManage.Replay\",\"visContextKey\":\"creatorMenu.replay_auth\",\"visContextValue\":true},{\"path\":\"/content/live/replay\",\"name\":\"\u76f4\u64ad\u56de\u653e\",\"key\":\"CreatorCommonManage.Replay\"}]}]}}"}, {"name": "SLARDARpassport_token_beat", "value": "JTdCJTIydXNlcklkJTIyOiUyMkxrZGllMDAxJTIyLCUyMmRldmljZUlkJTIyOiUyMmZhYjZmNTZiLWRhNmMtNDljOS1iZmU5LTZmN2Q2YzkxMzEwMiUyMiwlMjJleHBpcmVzJTIyOjE3ODA4NDEyMjgxNzIlN0Q="}, {"name": "__tea_cache_first_2906", "value": "1"}, {"name": "__tea_cache_tokens_5231", "value": "{\"web_id\":\"7615257052797519387\",\"user_unique_id\":\"7615257052797519387\",\"timestamp\":1773065219168,\"_type_\":\"default\"}"}, {"name": "ztsdk_tcc_config", "value": "{\"value\":{\"ztsdk_config\":{\"2906\":[{\"aid\":2906,\"scene\":\"web_protect\",\"certType\":\"cookie\",\"providerPathList\":[],\"consumerPathList\":[\"/aweme/v1/creator/relation/create/\",\"/web/api/v2/creator/activity/collect/\",\"/live/api/room/create_media_room/\",\"/aweme/janus/creator/comment/aweme/v1/web/comment/multi_publish/\",\"/aweme/v1/web/comment/multi_publish/\",\"/aweme/janus/creator/comment/aweme/v1/comment/publish/\",\"/aweme/v1/web/comment/publish/\",\"/aweme/janus/creator/comment/aweme/v1/web/comment/digg/\",\"/aweme/v1/web/comment/digg/\",\"/aweme/janus/creator/comment/aweme/v1/web/comment/multi_delete/\",\"/aweme/v1/web/comment/multi_delete/\",\"/aweme/v1/creator/comment/reply/\",\"/aweme/v1/creator/comment/action/\"],\"signVersion\":2}],\"6383\":[{\"aid\":6383,\"scene\":\"web_protect\",\"certType\":\"cookie\",\"consumerPathList\":[\"/aweme/v1/web/comment/list/reply/\",\"/aweme/v1/web/comment/list/\",\"/aweme/v2/web/comment/list/reply/\"],\"signVersion\":2}]}},\"expire\":1773086761829}"}, {"name": "__tea_sdk_ab_version_2906", "value": "{\"ab_version\":[\"90613695\",\"90112463\"],\"ab_ext_version\":[],\"ab_version_multilink\":[],\"data\":{\"ai_gen_cover\":{\"val\":1,\"vid\":\"15080732\"},\"assistantImage\":{\"val\":1,\"vid\":\"90116780\"},\"chapter_recommend_after_publish\":{\"val\":0,\"vid\":\"14187473\"},\"chapter_sync_reach\":{\"val\":1,\"vid\":\"91050166\"},\"chapter_video_status\":{\"val\":2,\"vid\":\"90953361\"},\"co_create_new_role\":{\"val\":true,\"vid\":\"90611581\"},\"collection_enable_set_secret_status\":{\"val\":1,\"vid\":\"90124858\"},\"cover_editor_interaction_opti\":{\"val\":2,\"vid\":\"90599399\"},\"cover_filling_rate_increase_all\":{\"val\":1,\"vid\":\"15074841\"},\"cover_h265_ffmpeg_capture\":{\"val\":true,\"vid\":\"90166065\"},\"creatorDynamicConfigV3\":{\"val\":{\"dynamicSliceRule\":{\"highSize\":10485760,\"lowNormalThreshold\":200,\"lowSize\":3145728,\"normalHighThreshold\":400,\"normalSize\":5242880},\"enableDynamicSlice\":true,\"enableDynamicSliceMinFileSize\":52428800,\"enableRouteSelect\":false},\"vid\":\"91210179\"},\"creator_comment_manage\":{\"val\":1,\"vid\":\"90613695\"},\"creator_danmaku_manage\":{\"val\":{\"menus_show\":1},\"vid\":\"90118007\"},\"creator_data_bullet_analysis\":{\"val\":{\"bullet_analysis_tab_show\":2},\"vid\":\"90128271\"},\"creator_data_new\":{\"val\":true,\"vid\":\"90081040\"},\"creator_data_progress_analysis\":{\"val\":{\"fans_analysis_tab_show\":1},\"vid\":\"90128191\"},\"creator_pc\":{\"val\":{\"tab_ab_show_new_home\":1},\"vid\":\"90109488\"},\"creator_pc_1_3_intell_chapter\":{\"val\":1,\"vid\":\"90122502\"},\"creator_pc_clip\":{\"val\":{\"capcut\":1},\"vid\":\"90119963\"},\"creator_pc_cover_min_size\":{\"val\":2,\"vid\":\"90126311\"},\"creator_pc_data_center\":{\"val\":{\"is_new_live_content\":1},\"vid\":\"90111860\"},\"creator_pc_data_center_new_list\":{\"val\":1,\"vid\":\"90625082\"},\"creator_pc_game_mounted_anchor_reedit\":{\"val\":1,\"vid\":\"90122407\"},\"creator_pc_game_mounted_anchor_revision_v2\":{\"val\":2,\"vid\":\"90125303\"},\"creator_pc_game_mounted_game_activity_profit\":{\"val\":3,\"vid\":\"90127201\"},\"creator_pc_home\":{\"val\":{\"interactive_show_type\":2},\"vid\":\"90111556\"},\"creator_pc_image_music_optimization\":{\"val\":1,\"vid\":\"90962255\"},\"creator_pc_mission\":{\"val\":{\"visible\":1},\"vid\":\"90112463\"},\"creator_pc_modify_item_entrance\":{\"val\":1,\"vid\":\"90128660\"},\"creator_pc_new_cover_text\":{\"val\":true,\"vid\":\"90125008\"},\"creator_pc_org\":{\"val\":{\"new_org\":1},\"vid\":\"90123271\"},\"creator_pc_org_home\":{\"val\":{\"new_home\":1},\"vid\":\"90112354\"},\"creator_pc_recommend_cover_opt\":{\"val\":true,\"vid\":\"90127437\"},\"creator_pc_tab_ab\":{\"val\":{\"account_show\":2},\"vid\":\"90092974\"},\"creator_pc_upload_cancel_btn\":{\"val\":0,\"vid\":\"90173282\"},\"creator_pc_upload_progress_detail\":{\"val\":1,\"vid\":\"90173281\"},\"creator_pc_video_music_optimization\":{\"val\":1,\"vid\":\"90945842\"},\"creator_pc_video_quality_intro\":{\"val\":2,\"vid\":\"91037348\"},\"creator_pc_work_detail_version\":{\"val\":1,\"vid\":\"90356254\"},\"creator_publish_cover_check\":{\"val\":1,\"vid\":\"90122334\"},\"creator_school_nav\":{\"val\":{\"nav_type\":1},\"vid\":\"90081895\"},\"creator_upload_async\":{\"val\":1,\"vid\":\"90324260\"},\"creator_upload_cancel\":{\"val\":1,\"vid\":\"90124601\"},\"creator_uploader_tos_direct\":{\"val\":1,\"vid\":\"90126365\"},\"data_cover_rate\":{\"val\":{\"is_show_cover_rate\":true},\"vid\":\"90121311\"},\"dict\":{\"val\":{\"canShowUserDeclaration\":true},\"vid\":\"90114001\"},\"douyin_cover_imagelayer_editable\":{\"val\":true,\"vid\":\"90111484\"},\"douyin_outer_progress_enbale\":{\"val\":1,\"vid\":\"90118893\"},\"douyin_pc_creator_image_text\":{\"val\":true,\"vid\":\"90093311\"},\"download_frame_limit\":{\"val\":3,\"vid\":\"91217036\"},\"editor_filter\":{\"val\":true,\"vid\":\"90122909\"},\"enableChapterSelfMade\":{\"val\":1,\"vid\":\"90940916\"},\"enhance_cover_check_dual\":{\"val\":1,\"vid\":\"90906860\"},\"is_new_loki\":{\"val\":1,\"vid\":\"90975626\"},\"is_show_pc_content_analysis\":{\"val\":{\"enabled\":1},\"vid\":\"90118423\"},\"is_show_tagged_page\":{\"val\":true,\"vid\":\"90992719\"},\"limit_recommend_cover_local\":{\"val\":true,\"vid\":\"91283345\"},\"maxReadConcurrent\":{\"val\":5,\"vid\":\"91210181\"},\"openReadConcurrent\":{\"val\":true,\"vid\":\"91210180\"},\"publish_layout\":{\"val\":2,\"vid\":\"91020340\"},\"quick_fill\":{\"val\":true,\"vid\":\"90997687\"},\"recommend_chapter_async_link\":{\"val\":1,\"vid\":\"90129292\"},\"refine_cover_tool\":{\"val\":2,\"vid\":\"90120266\"},\"show_activity_tag\":{\"val\":true,\"vid\":\"90102355\"},\"show_info\":{\"val\":1,\"vid\":\"90126292\"},\"show_intellect_chapter_entry\":{\"val\":1,\"vid\":\"90094899\"},\"slardar_filter\":{\"val\":true,\"vid\":\"90122697\"},\"table_tab\":{\"val\":\"contribute\",\"vid\":\"90094565\"},\"test\":{\"val\":true,\"vid\":\"90082519\"},\"title_permission_new\":{\"val\":1,\"vid\":\"90111481\"},\"use_new_player\":{\"val\":true,\"vid\":\"91048272\"},\"vertical_video_double_cover\":{\"val\":1,\"vid\":\"91234029\"}},\"timestamp\":1773065219091,\"uuid\":\"95519194897\"}"}, {"name": "security-sdk/s_sdk_server_cert_key", "value": "{\"cert\":\"-----BEGIN CERTIFICATE-----\\nMIIEfTCCBCKgAwIBAgIUXWdS2tzmSoewCWfKFyiWMrJqs/0wCgYIKoZIzj0EAwIw\\nMTELMAkGA1UEBhMCQ04xIjAgBgNVBAMMGXRpY2tldF9ndWFyZF9jYV9lY2RzYV8y\\nNTYwIBcNMjIxMTE4MDUyMDA2WhgPMjA2OTEyMzExNjAwMDBaMCQxCzAJBgNVBAYT\\nAkNOMRUwEwYDVQQDEwxlY2llcy1zZXJ2ZXIwWTATBgcqhkjOPQIBBggqhkjOPQMB\\nBwNCAASE2llDPlfc8Rq+5J5HXhg4edFjPnCF3Ua7JBoiE/foP9m7L5ELIcvxCgEx\\naRCHbQ8kCCK/ArZ4FX/qCobZAkToo4IDITCCAx0wDgYDVR0PAQH/BAQDAgWgMDEG\\nA1UdJQQqMCgGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwME\\nMCkGA1UdDgQiBCABydxqGrVEHhtkCWTb/vicGpDZPFPDxv82wiuywUlkBDArBgNV\\nHSMEJDAigCAypWfqjmRIEo3MTk1Ae3MUm0dtU3qk0YDXeZSXeyJHgzCCAZQGCCsG\\nAQUFBwEBBIIBhjCCAYIwRgYIKwYBBQUHMAGGOmh0dHA6Ly9uZXh1cy1wcm9kdWN0\\naW9uLmJ5dGVkYW5jZS5jb20vYXBpL2NlcnRpZmljYXRlL29jc3AwRgYIKwYBBQUH\\nMAGGOmh0dHA6Ly9uZXh1cy1wcm9kdWN0aW9uLmJ5dGVkYW5jZS5uZXQvYXBpL2Nl\\ncnRpZmljYXRlL29jc3AwdwYIKwYBBQUHMAKGa2h0dHA6Ly9uZXh1cy1wcm9kdWN0\\naW9uLmJ5dGVkYW5jZS5jb20vYXBpL2NlcnRpZmljYXRlL2Rvd25sb2FkLzQ4RjlD\\nMEU3QjBDNUE3MDVCOTgyQkU1NTE3MDVGNjQ1QzhDODc4QTguY3J0MHcGCCsGAQUF\\nBzAChmtodHRwOi8vbmV4dXMtcHJvZHVjdGlvbi5ieXRlZGFuY2UubmV0L2FwaS9j\\nZXJ0aWZpY2F0ZS9kb3dubG9hZC80OEY5QzBFN0IwQzVBNzA1Qjk4MkJFNTUxNzA1\\nRjY0NUM4Qzg3OEE4LmNydDCB5wYDVR0fBIHfMIHcMGygaqBohmZodHRwOi8vbmV4\\ndXMtcHJvZHVjdGlvbi5ieXRlZGFuY2UuY29tL2FwaS9jZXJ0aWZpY2F0ZS9jcmwv\\nNDhGOUMwRTdCMEM1QTcwNUI5ODJCRTU1MTcwNUY2NDVDOEM4NzhBOC5jcmwwbKBq\\noGiGZmh0dHA6Ly9uZXh1cy1wcm9kdWN0aW9uLmJ5dGVkYW5jZS5uZXQvYXBpL2Nl\\ncnRpZmljYXRlL2NybC80OEY5QzBFN0IwQzVBNzA1Qjk4MkJFNTUxNzA1RjY0NUM4\\nQzg3OEE4LmNybDAKBggqhkjOPQQDAgNJADBGAiEAqMjT5ADMdGMeaImoJK4J9jzE\\nLqZ573rNjsT3k14pK50CIQCLpWHVKWi71qqqrMjiSDvUhpyO1DpTPRHlavPRuaNm\\nww==\\n-----END CERTIFICATE-----\",\"sn\":\"533240336124694022040808462028007165443034493949\",\"createdTime\":1773065162384}"}, {"name": "__tea_cache_first_1661", "value": "1"}, {"name": "SLARDARdouyin_creator", "value": "JTdCJTIydXNlcklkJTIyOiUyMjk1NTE5MTk0ODk3JTIyLCUyMmRldmljZUlkJTIyOiUyMmI5YzcyOGE1LTZlMzEtNDhmOC1hOGQwLTc1MjllM2YxZDUyMCUyMiwlMjJleHBpcmVzJTIyOjE3ODA4NDEyMTc2OTUlN0Q="}, {"name": "__tea_cache_first_408314", "value": "1"}, {"name": "https://creator.douyin.com-operation", "value": "true"}, {"name": "security-sdk/s_sdk_pub_key", "value": "{\"key\":\"security-sdk/s_sdk_pub_key\",\"data\":\"-----BEGIN PUBLIC KEY-----\\r\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErCHJ92kQcQ0VAHrXeTaU+6LveKle\\r\\ndQkSK9l4R+5/8ecsWq8XkDfV77fQA8zUsjRff59KsLNXmPFMYugNFnCW+w==\\r\\n-----END PUBLIC KEY-----\\r\\n\",\"createTime\":1773065219342,\"expireTime\":4102444799000}"}, {"name": "__tea_cache_tokens_408314", "value": "{\"web_id\":\"7615257084133967386\",\"user_unique_id\":\"7615257084133967386\",\"timestamp\":1773065219153,\"_type_\":\"default\"}"}, {"name": "security-sdk/s_sdk_cert_key", "value": "{\"data\":\"pub.BIbOIkvm2Eqd3A3iArDLRR/0UGDzkjlwbTL5HYSh3UMLa5XUgjrUTkgAAYjFmC+Evis4DpXWoT+xQHhRPeJiMhU=\"}"}, {"name": "__tea_cache_tokens_1661", "value": "{\"web_id\":\"6108266137339144331\",\"user_unique_id\":\"95519194897\",\"timestamp\":1773065217945,\"_type_\":\"default\"}"}, {"name": "=^_^=athena_web_id", "value": "c058269a-800e-491f-88aa-b88c6ffc8783"}, {"name": "SLARDARmfa_web", "value": "JTdCJTIydXNlcklkJTIyOiUyMmNiNDZiMTJhLTc1NTktNDFmNy05NDU3LTkwMzc2NDk0OGVmMiUyMiwlMjJkZXZpY2VJZCUyMjolMjI2MzZmYTg3NC00YzgzLTQ3MjAtOGI5Ny01YWQxNTBlYjE2NTklMjIsJTIyZXhwaXJlcyUyMjoxNzgwODQxMTkzNDczJTdE"}, {"name": "security-sdk/s_sdk_crypt_sdk", "value": "{\"data\":\"{\\\"ec_privateKey\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\r\\\\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjSf1lDtMRD5PmlYz\\\\r\\\\nl2iu1o0fprncRpnwWTSR36I298uhRANCAASGziJL5thKndwN4gKwy0Uf9FBg85I5\\\\r\\\\ncG0y+R2Eod1DC2uV1II61E5IAAGIxZgvhL4rOA6V1qE/sUB4UT3iYjIV\\\\r\\\\n-----END PRIVATE KEY-----\\\\r\\\\n\\\",\\\"ec_publicKey\\\":\\\"-----BEGIN PUBLIC KEY-----\\\\r\\\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhs4iS+bYSp3cDeICsMtFH/RQYPOS\\\\r\\\\nOXBtMvkdhKHdQwtrldSCOtROSAABiMWYL4S+KzgOldahP7FAeFE94mIyFQ==\\\\r\\\\n-----END PUBLIC KEY-----\\\\r\\\\n\\\",\\\"ec_csr\\\":\\\"\\\"}\"}"}, {"name": "__tea_cache_tokens_2906", "value": "{\"user_unique_id\":\"95519194897\",\"web_id\":\"7615256846605321747\",\"timestamp\":1773065218559,\"_type_\":\"default\"}"}, {"name": "__tea_cache_first_5231", "value": "1"}, {"name": "security-sdk/s_sdk_pri_key", "value": "{\"key\":\"security-sdk/s_sdk_pri_key\",\"data\":\"-----BEGIN PRIVATE KEY-----\\r\\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgbpMUb335gEQ6ALO1\\r\\n0mus3hjzs868Ecz2SlryWs7lmEOhRANCAASsIcn3aRBxDRUAetd5NpT7ou94qV51\\r\\nCRIr2XhH7n/x5yxarxeQN9Xvt9ADzNSyNF9/n0qws1eY8Uxi6A0WcJb7\\r\\n-----END PRIVATE KEY-----\\r\\n\",\"createTime\":1773065219342,\"expireTime\":4102444799000}"}, {"name": "xmst", "value": "9G3pbFh2dIlxjtWujodOmQWLukWEL9l06N1JOrpYrTsU-M8EDcMNxDiNKseQ7YKdCnyxVteUCAt5vnETCh34267vINvDLOsBnydDbHJeGSj6DYhYYmLJ8dhXofm_honbYVGF85-hQZtIMRSmTR-xTqNHJQlzz8m-YLWD_aaXtjZspIp28GE60w=="}, {"name": "web_secsdk_runtime_cache", "value": "{\"userAgentCheckReport\":[\"userAgentCheckReportcompute\"]}"}, {"name": "security-sdk/s_sdk_sign_data_key/web_protect", "value": "{\"data\":\"{\\\"ticket\\\":\\\"hash.Yt8KY6OAq5OQ5FfncQX28oPawseU0rfoz5GZpsYAuFU=\\\",\\\"ts_sign\\\":\\\"ts.2.e1b362f7151e8ae9bbef7db7e3e462cd8b06b010ef7347441db13a35a3625b53c4fbe87d2319cf05318624ceda14911ca406dedbebeddb2e30fce8d4fa02575d\\\",\\\"client_cert\\\":\\\"pub.BIbOIkvm2Eqd3A3iArDLRR/0UGDzkjlwbTL5HYSh3UMLa5XUgjrUTkgAAYjFmC+Evis4DpXWoT+xQHhRPeJiMhU=\\\",\\\"log_id\\\":\\\"20260309220656B9D5892F827F40BBF82D\\\",\\\"create_time\\\":1773065217}\"}"}, {"name": "SLARDARuc_secure_sdk", "value": "JTdCJTIydXNlcklkJTIyOiUyMjcwMzMzZDAyLWRiZmItNGI3Mi1iMjM4LWI1NDk5ZGFiYzhmNSUyMiwlMjJkZXZpY2VJZCUyMjolMjJhN2RlYTRlZi1mMzg4LTRlNjMtYWZiNC1jMGRiOWViMTFjNzclMjIsJTIyZXhwaXJlcyUyMjoxNzgwODQxMjE3OTQxJTdE"}, {"name": "douyin_creator_master_performance_level", "value": "{}"}, {"name": "security-sdk/s_sdk_sign_data_key/token", "value": "{\"key\":\"security-sdk/s_sdk_sign_data_key/token\",\"data\":\"{\\\"ts_sign\\\":\\\"#P9+o09uPziH2CdaPqLym6AHR1o/dJi2CbbkGgg54Nfn4HS7KydO6wEwXZYeEyxUQVUX3j/+KGz2m1VqhLY7b/DfOKiufTeYeO1EP+aJ2pixUbffGKigieH8co1g8tJdvNfFkG98vaDzSF5wi9pSGChpFJ0Z+XDRJBZn1phwwFuWGXfcgi2VNeRAjcXfEK3lb0lzNgn9CQN1eMkCibpBePTBgGFjaqOdctktsSlGu5PDXw3Nr5mJMezM9llIOBlWiet6NpbyzZIu8MglZMYo5iwGikS70Y3mRbq8fixei2GgsTc7HFRGFp9G+UZNMWgFCJhMhwhzELRnVIICK+WT6ocIMeFs/6GkwVrwWAfCugzsABY3givrAX3zMpqINeb8RryQhUWtfg6mOSGVmRdF98BIAFSd7xe+56Qlxnqha47qJSP+pzi5SOiAw5Z96YgHHc0FmZ7o132H4Ou9N7v7VS7NXcdQ2s+yXLzAOm/E0lpAeBla0YUQXpW21qfjAxJSKHxVRYw5zsqOVY5jMh2fOIjVxTMjMkOOKTOJUQxBG3Swdw+cQqKano0ARzwP/9KQO8Pc4Lf2+8NjAtON6l580C+un/4+Zuq0K5OoCvwIkrf8pbc/vL4iyktGJj7IG80uugNbp7RDnFLFMSHP6BsdEN4XG3rKaDfP19hw8P43VIEE8+/88RYBTepw5Tkhtb2snfmpn3t6OMJzkEEk9g55yCklziZT1sHt7/BI4NZr79dJqSNRgq+LtvSPs3LqemhkEr/YgPjxFnF4NNHmWzLxYZ8yaw3dS2LyNLa2hySUhEogUFoukfpbZugtB2q262vmJOwqWBgypwcY3rlPwZKTwkeppFoT0++uQVYcS5eBvcg6LIpyCTUymuVRSqQ4nu2NywPntvpt7nFOP2h8mMc5GEryZhxbQROZyX+OsPCpqSwFda1/NB3htjfKx0dV3WbnDCnPwEqt9ebHeXL/Rpam0/jS7rh8dibvljTt0cqgLQJhBlr8Oj+l3sAgR7/fkezFyhc9z0UCFagXAQofUP/cSibm0v8pd9KsKuyEupu9ce3ZTcTcbdCeKBnq5/igjzStyM7LMj1tVe/fyhDa91yUpohuouyqoNCBtKMr+NAtN4f6tncEQoMEGKs6ro8qwEKOlkBYJdrpUASAxnpCLamqx/5EVnNuNdulG8usrtal7x4vEIGDv1Wd3yR2iSXjoUyZTfh5mGRKhFlrrjofP6/SOJWA6S0mMzEpD05DZSU7ubgkkDHmhd1ogTlTlpWqDBQ==\\\",\\\"ticket\\\":\\\"YSHGAwEBBwECBE0kqOYBZwFVfr2lhVBCQQa6lFUrUQ7LpCsCQkPAk2IFM4lsF/nPOIBNKklQqK+Ua4Tix/gXuTfxhSpsgh+d4YY3rypM7ON794TrWoC29ViefPTJFWhYaxJ17AhW4A==\\\",\\\"client_cert\\\":\\\"-----BEGIN CERTIFICATE-----\\\\nMIICGjCCAb+gAwIBAgIVAIEpKZTb1t7xc4MzsRibMWxQ+t6lMAoGCCqGSM49BAMC\\\\nMDExCzAJBgNVBAYTAkNOMSIwIAYDVQQDDBl0aWNrZXRfZ3VhcmRfY2FfZWNkc2Ff\\\\nMjU2MB4XDTI2MDMwOTE0MDY1OVoXDTM2MDMwOTIyMDY1OVowJzELMAkGA1UEBhMC\\\\nQ04xGDAWBgNVBAMMD2JkX3RpY2tldF9ndWFyZDBZMBMGByqGSM49AgEGCCqGSM49\\\\nAwEHA0IABKwhyfdpEHENFQB613k2lPui73ipXnUJEivZeEfuf/HnLFqvF5A31e+3\\\\n0APM1LI0X3+fSrCzV5jxTGLoDRZwlvujgb0wgbowDgYDVR0PAQH/BAQDAgWgMDEG\\\\nA1UdJQQqMCgGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwME\\\\nMCkGA1UdDgQiBCBjIGOdMivRl+Akkn2YoVLN+O9GdutthtcfMsl92DifFTArBgNV\\\\nHSMEJDAigCAypWfqjmRIEo3MTk1Ae3MUm0dtU3qk0YDXeZSXeyJHgzAdBgNVHREE\\\\nFjAUghJjcmVhdG9yLmRvdXlpbi5jb20wCgYIKoZIzj0EAwIDSQAwRgIhAMc2e/u9\\\\nt42L63GkACNb6Aji6NKM5avcOC6E+2AlCnp5AiEAxTRkhc/H1rewNEiBT4L/Bwi4\\\\nZ8lRSC1dH3hwm8qA/CU=\\\\n-----END CERTIFICATE-----\\\\n\\\"}\",\"createTime\":1773065219527,\"expireTime\":4102444799000}"}]}, {"origin": "https://lf-zt.douyin.com", "localStorage": [{"name": "security-sdk/s_sdk_cert_key", "value": "{\"data\":\"pub.BIbOIkvm2Eqd3A3iArDLRR/0UGDzkjlwbTL5HYSh3UMLa5XUgjrUTkgAAYjFmC+Evis4DpXWoT+xQHhRPeJiMhU=\"}"}, {"name": "security-sdk/s_sdk_sign_data_key/web_protect", "value": "{\"data\":\"{\\\"ticket\\\":\\\"hash.Yt8KY6OAq5OQ5FfncQX28oPawseU0rfoz5GZpsYAuFU=\\\",\\\"ts_sign\\\":\\\"ts.2.e1b362f7151e8ae9bbef7db7e3e462cd8b06b010ef7347441db13a35a3625b53c4fbe87d2319cf05318624ceda14911ca406dedbebeddb2e30fce8d4fa02575d\\\",\\\"client_cert\\\":\\\"pub.BIbOIkvm2Eqd3A3iArDLRR/0UGDzkjlwbTL5HYSh3UMLa5XUgjrUTkgAAYjFmC+Evis4DpXWoT+xQHhRPeJiMhU=\\\",\\\"log_id\\\":\\\"20260309220656B9D5892F827F40BBF82D\\\",\\\"create_time\\\":1773065217}\"}"}, {"name": "security-sdk/s_sdk_crypt_sdk", "value": "{\"data\":\"{\\\"ec_privateKey\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\r\\\\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjSf1lDtMRD5PmlYz\\\\r\\\\nl2iu1o0fprncRpnwWTSR36I298uhRANCAASGziJL5thKndwN4gKwy0Uf9FBg85I5\\\\r\\\\ncG0y+R2Eod1DC2uV1II61E5IAAGIxZgvhL4rOA6V1qE/sUB4UT3iYjIV\\\\r\\\\n-----END PRIVATE KEY-----\\\\r\\\\n\\\",\\\"ec_publicKey\\\":\\\"-----BEGIN PUBLIC KEY-----\\\\r\\\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhs4iS+bYSp3cDeICsMtFH/RQYPOS\\\\r\\\\nOXBtMvkdhKHdQwtrldSCOtROSAABiMWYL4S+KzgOldahP7FAeFE94mIyFQ==\\\\r\\\\n-----END PUBLIC KEY-----\\\\r\\\\n\\\",\\\"ec_csr\\\":\\\"\\\"}\"}"}]}, {"origin": "https://summon.bytedance.com", "localStorage": [{"name": "10001_24_show_service_window", "value": ""}, {"name": "SLARDARcard_factory", "value": "JTdCJTIydXNlcklkJTIyOiUyMmM0ZTAwYzgxLWE3M2QtNDg5Yy04OWVkLWE2ZjkzZjYzNDRmYSUyMiwlMjJkZXZpY2VJZCUyMjolMjJiODcyYTY1Zi02YWE5LTRmMTAtODA4Mi0xYzZmODYyMmIzM2MlMjIsJTIyZXhwaXJlcyUyMjoxNzgwODQxMjE5MjMyJTdE"}, {"name": "__tea_cache_refer_648684", "value": "{\"refer_key\":\"\",\"refer_title\":\"/web/\",\"refer_manual_key\":\"\",\"routeChange\":false}"}, {"name": "__tea_cache_tokens_648684", "value": "{\"web_id\":\"7615257081709315634\",\"user_unique_id\":\"7615257081709315634\",\"timestamp\":1773065219135,\"_type_\":\"default\"}"}, {"name": "__tea_cache_first_648684", "value": "1"}, {"name": "byted_im_tk", "value": "{\"1773065219056206\":\"b9df83f48ff3ce80288c64436619de8c\"}"}, {"name": "__tea_cache_first_2970", "value": "1"}, {"name": "__tea_cache_tokens_2970", "value": "{\"web_id\":\"7615257088306103817\",\"user_unique_id\":\"7615257088306103817\",\"timestamp\":1773065219077,\"_type_\":\"default\"}"}]}]} \ No newline at end of file diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/error_1.png b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/error_1.png new file mode 100644 index 00000000..7521031f Binary files /dev/null and b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/error_1.png differ diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/intercept_create_v2.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/intercept_create_v2.py new file mode 100644 index 00000000..13ae6c3a --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/intercept_create_v2.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +""" +精准拦截 create_v2 请求 — 捕获所有 POST 请求以找到真正的发布 API。 +使用 page.route() 拦截(更可靠)+ page.on('request') 双保险。 +""" +import asyncio +import json +import os +import sys +from pathlib import Path + +from playwright.async_api import async_playwright, Request, Route + +SCRIPT_DIR = Path(__file__).parent +COOKIE_FILE = SCRIPT_DIR / "douyin_storage_state.json" +STEALTH_JS = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend/utils/stealth.min.js") +VIDEO = "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/广点通能投Soul了,1000曝光6到10块.mp4" +LOG_FILE = SCRIPT_DIR / "create_v2_captured.json" +ALL_POSTS_FILE = SCRIPT_DIR / "all_post_requests.json" +CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + +captured_creates = [] +all_post_after_click = [] +publishing_started = False + + +def on_request(request: Request): + global publishing_started + url = request.url + if request.method != "POST": + return + if not publishing_started: + return + if any(skip in url for skip in ["monitor_browser", "mcs.snssdk", "mon.zijie", "mssdk", ".css", ".js", ".png", "ttwid/check", "goofy_worker", "passport/user_info"]): + return + + entry = { + "url": url[:600], + "method": request.method, + "content_type": request.headers.get("content-type", ""), + } + try: + body = request.post_data + if body: + entry["body"] = body[:5000] + entry["body_length"] = len(body) + except Exception: + entry["body"] = None + + all_post_after_click.append(entry) + short_url = url.split("?")[0] + print(f" [POST] {short_url}") + + if any(kw in url for kw in ["create_v2", "aweme/create", "aweme/post"]): + entry["headers"] = dict(request.headers) + try: + body = request.post_data + entry["full_body"] = body[:10000] if body else None + except Exception: + pass + + captured_creates.append(entry) + print(f"\n{'#'*60}") + print(f" ★★★ 捕获到 create 请求! ★★★") + print(f" URL: {url}") + print(f" Content-Type: {entry['content_type']}") + if entry.get("full_body"): + print(f" Body ({len(entry['full_body'])} chars):") + print(f" {entry['full_body'][:3000]}") + print(f"{'#'*60}\n") + + with open(LOG_FILE, "w", encoding="utf-8") as f: + json.dump(captured_creates, f, ensure_ascii=False, indent=2) + + +async def main(): + global publishing_started + + if not COOKIE_FILE.exists(): + print("[!] Cookie 不存在") + return 1 + + async with async_playwright() as pw: + browser = await pw.chromium.launch( + headless=False, + executable_path=CHROME if os.path.exists(CHROME) else None, + ) + context = await browser.new_context(storage_state=str(COOKIE_FILE)) + if STEALTH_JS.exists(): + await context.add_init_script(path=str(STEALTH_JS)) + + page = await context.new_page() + page.on("request", on_request) + + print("[1] 打开上传页...") + await page.goto("https://creator.douyin.com/creator-micro/content/upload", + wait_until="domcontentloaded", timeout=60000) + await page.wait_for_url("**/upload**", timeout=60000) + await page.wait_for_load_state("load", timeout=20000) + + if await page.get_by_text("手机号登录").count() or await page.get_by_text("扫码登录").count(): + print("[!] Cookie 失效") + await browser.close() + return 1 + print("[1] OK") + await asyncio.sleep(3) + + print("[2] 上传视频...") + loc = page.locator("input[type='file']").first + await loc.wait_for(state="attached", timeout=10000) + await loc.set_input_files(VIDEO, timeout=60000) + print("[2] OK") + + print("[3] 等待发布页...") + for _ in range(120): + if "publish" in page.url or "post/video" in page.url: + break + await asyncio.sleep(1) + print(f"[3] URL: {page.url}") + await asyncio.sleep(5) + + print("[4] 填标题...") + try: + nl = page.locator(".notranslate").first + await nl.click(timeout=5000) + await page.keyboard.press("Meta+KeyA") + await page.keyboard.press("Delete") + await page.keyboard.type("广点通能投Soul了 测试发布", delay=20) + print("[4] OK") + except Exception as e: + print(f"[4] 异常: {e}") + + print("[5] 等待视频上传+转码完成...") + for i in range(240): + try: + c1 = await page.locator('div:has-text("重新上传")').count() + c2 = await page.locator('text="上传完成"').count() + c3 = await page.locator('[class*="success"]').count() + if c1 > 0 or c2 > 0: + break + except Exception: + pass + if i % 15 == 0 and i > 0: + print(f" ...{i}s") + await asyncio.sleep(1) + + # 额外等待转码 + print("[5] 上传完成,等待转码...") + await asyncio.sleep(15) + + # 检查视频是否可发布 + try: + enabled = await page.locator('text="视频解析中"').count() + if enabled > 0: + print("[5] 仍在解析,继续等...") + for i in range(60): + c = await page.locator('text="视频解析中"').count() + if c == 0: + break + await asyncio.sleep(2) + except Exception: + pass + print("[5] OK") + + # 开始监控 + publishing_started = True + print("\n" + "="*60) + print(" ★ 开始监控所有 POST 请求 — 即将点击发布 ★") + print("="*60 + "\n") + + # 多次尝试发布 + for attempt in range(10): + print(f"\n--- 发布尝试 #{attempt+1} ---") + try: + pub = page.get_by_role("button", name="发布", exact=True) + if await pub.count(): + await pub.click() + print(f"[6] 已点击发布") + else: + pub2 = page.locator("button:has-text('发布')").first + if await pub2.count(): + await pub2.click() + print(f"[6] 已点击发布(fallback)") + except Exception as e: + print(f"[6] 点击异常: {e}") + + # 等待 + await asyncio.sleep(5) + + # 检查是否跳转 + if "manage" in page.url: + print("[✓] 页面已跳转到管理页 — 发布成功!") + break + + # 处理封面弹窗 + try: + if await page.get_by_text("请设置封面后再发布").first.is_visible(): + print("[6] 需要封面,选择推荐封面...") + covers = page.locator('[class*="recommendCover"], [class*="cover-select"] img, [class*="coverCard"]') + if await covers.count() > 0: + await covers.first.click() + await asyncio.sleep(2) + confirm = page.get_by_role("button", name="确定") + if await confirm.count(): + await confirm.click() + await asyncio.sleep(3) + print("[6] 封面已选,继续...") + continue + except Exception: + pass + + # 处理验证弹窗 + try: + if await page.locator('text="安全验证"').count() > 0: + print("[!] 触发安全验证,无法自动处理") + break + except Exception: + pass + + await asyncio.sleep(5) + + if captured_creates: + print("[✓] 已捕获 create 请求!") + break + + # 最终等待 + print("\n[7] 最终等待...") + await asyncio.sleep(10) + + print(f"\n{'='*60}") + print(f" 最终结果") + print(f" URL: {page.url}") + print(f" 捕获 create 请求: {len(captured_creates)}") + print(f" 捕获所有 POST: {len(all_post_after_click)}") + print(f"{'='*60}") + + # 保存所有 POST 请求 + with open(ALL_POSTS_FILE, "w", encoding="utf-8") as f: + json.dump(all_post_after_click, f, ensure_ascii=False, indent=2) + print(f"[i] 所有 POST 请求已保存: {ALL_POSTS_FILE}") + + if captured_creates: + with open(LOG_FILE, "w", encoding="utf-8") as f: + json.dump(captured_creates, f, ensure_ascii=False, indent=2) + print(f"[i] create 请求已保存: {LOG_FILE}") + + # 打印所有 POST 摘要 + print("\n--- 所有 POST 请求摘要 ---") + for i, req in enumerate(all_post_after_click): + url_short = req["url"].split("?")[0] + ct = req.get("content_type", "") + bl = req.get("body_length", 0) + print(f" {i+1}. {url_short} [{ct}] body={bl}b") + + await context.storage_state(path=str(COOKIE_FILE)) + await context.close() + await browser.close() + + return 0 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/intercept_publish.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/intercept_publish.py new file mode 100644 index 00000000..bfab5107 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/intercept_publish.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +""" +拦截抖音创作者中心视频发布的网络请求。 +用 Playwright 走一次完整的上传→发布流程,记录所有 API 调用。 +""" +import asyncio +import json +import os +import sys +from datetime import datetime +from pathlib import Path + +from playwright.async_api import async_playwright, Request + +SCRIPT_DIR = Path(__file__).parent +COOKIE_FILE = SCRIPT_DIR / "douyin_storage_state.json" +STEALTH_JS = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend/utils/stealth.min.js") +VIDEO = "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/广点通能投Soul了,1000曝光6到10块.mp4" +LOG_FILE = SCRIPT_DIR / "intercepted_requests.json" + +CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + +captured = [] + + +def on_request(request: Request): + url = request.url + if "creator.douyin.com" not in url and "bytedanceapi" not in url and "snssdk" not in url and "bytedancevod" not in url: + return + if any(skip in url for skip in ["monitor_browser", "mcs.snssdk", "mon.zijie", "mssdk.bytedance", ".css", ".js", ".png", ".jpg", ".woff"]): + return + + entry = { + "timestamp": datetime.now().isoformat(), + "method": request.method, + "url": url[:500], + "headers": dict(request.headers), + "post_data": None, + } + try: + pd = request.post_data + if pd: + entry["post_data"] = pd[:2000] if len(pd) > 2000 else pd + except Exception: + pass + + captured.append(entry) + short = url.split("?")[0] + print(f" [NET] {request.method} {short}") + + +async def main(): + if not COOKIE_FILE.exists(): + print("[!] Cookie 不存在,请先运行 douyin_login.py") + return 1 + + print("[i] 启动浏览器,拦截网络请求...") + async with async_playwright() as pw: + browser = await pw.chromium.launch( + headless=False, + executable_path=CHROME if os.path.exists(CHROME) else None, + ) + context = await browser.new_context(storage_state=str(COOKIE_FILE)) + if STEALTH_JS.exists(): + await context.add_init_script(path=str(STEALTH_JS)) + + page = await context.new_page() + page.on("request", on_request) + + # 1. 打开上传页 + print("[1] 打开上传页...") + await page.goto("https://creator.douyin.com/creator-micro/content/upload", wait_until="domcontentloaded", timeout=60000) + await page.wait_for_url("**/upload**", timeout=60000) + await page.wait_for_load_state("load", timeout=20000) + + if await page.get_by_text("手机号登录").count() or await page.get_by_text("扫码登录").count(): + print("[!] Cookie 失效") + await browser.close() + return 1 + print("[1] 上传页就绪") + + try: + await page.get_by_text("上传视频", exact=False).first.wait_for(state="visible", timeout=15000) + except Exception: + pass + await asyncio.sleep(3) + + # 2. 上传文件 + print("[2] 上传视频...") + for sel in ["input[type='file']", "[class^='upload-btn-input']"]: + try: + loc = page.locator(sel).first + await loc.wait_for(state="attached", timeout=5000) + await loc.set_input_files(VIDEO, timeout=60000) + print(f"[2] 上传成功: {sel}") + break + except Exception: + continue + + # 3. 等待发布页 + print("[3] 等待发布页...") + for _ in range(120): + try: + await page.wait_for_url("**/publish*", timeout=2000) + break + except Exception: + try: + await page.wait_for_url("**/post/video*", timeout=2000) + break + except Exception: + pass + print(f"[3] 当前URL: {page.url}") + await asyncio.sleep(3) + + # 4. 填标题 + print("[4] 填标题...") + try: + tc = page.get_by_text("作品标题").locator("..").locator("xpath=following-sibling::div[1]").locator("input") + if await tc.count(): + await tc.fill("广点通能投Soul了 #Soul派对 #测试") + else: + nl = page.locator(".notranslate").first + await nl.click(timeout=5000) + await page.keyboard.press("Meta+KeyA") + await page.keyboard.press("Delete") + await page.keyboard.type("广点通能投Soul了 #Soul派对 #测试", delay=20) + await page.keyboard.press("Enter") + except Exception as e: + print(f"[4] 标题异常: {e}") + print("[4] 标题已填") + + # 5. 等待上传完成 + print("[5] 等待上传完成...") + for _ in range(120): + try: + n = await page.locator('[class^="long-card"] div:has-text("重新上传")').count() + if n > 0: + break + except Exception: + pass + await asyncio.sleep(2) + print("[5] 上传完毕") + await asyncio.sleep(2) + + # 清空之前的记录,只保留发布相关 + print("\n" + "="*60) + print(" 即将点击发布,开始重点监控网络请求") + print("="*60 + "\n") + captured.clear() + + # 6. 点击发布 + print("[6] 点击发布...") + for attempt in range(20): + try: + pub = page.get_by_role("button", name="发布", exact=True) + if await pub.count(): + await pub.click() + print(f"[6] 已点击发布 (attempt {attempt+1})") + + await page.wait_for_url("**/content/manage**", timeout=8000) + print("[6] 发布成功!页面已跳转到管理页") + break + except Exception: + # 处理封面 + try: + if await page.get_by_text("请设置封面后再发布").first.is_visible(): + print("[6] 需要封面...") + cover = page.locator('[class^="recommendCover-"]').first + if await cover.count(): + await cover.click() + await asyncio.sleep(1) + if await page.get_by_text("是否确认应用此封面?").first.is_visible(): + await page.get_by_role("button", name="确定").click() + await asyncio.sleep(1) + except Exception: + pass + await asyncio.sleep(3) + + await asyncio.sleep(3) + + # 保存 Cookie + await context.storage_state(path=str(COOKIE_FILE)) + + # 保存拦截到的请求 + print(f"\n[i] 共捕获 {len(captured)} 个相关请求") + with open(LOG_FILE, "w", encoding="utf-8") as f: + json.dump(captured, f, ensure_ascii=False, indent=2) + print(f"[i] 已保存到: {LOG_FILE}") + + # 打印关键请求 + print("\n" + "="*60) + print(" 关键 POST 请求(发布相关)") + print("="*60) + for req in captured: + if req["method"] == "POST": + url_short = req["url"].split("?")[0] + print(f"\n POST {url_short}") + if req.get("post_data"): + print(f" Body: {req['post_data'][:300]}") + ct = req["headers"].get("content-type", "") + print(f" Content-Type: {ct}") + + await context.close() + await browser.close() + + return 0 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/local_video_server.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/local_video_server.py new file mode 100644 index 00000000..4f4f9eb5 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/local_video_server.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""本地视频服务器:用 CORS 提供视频文件给浏览器 fetch 下载。""" +import http.server +import os +import sys +from pathlib import Path + +PORT = 19876 +SERVE_DIR = "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片" + + +class CORSHandler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=SERVE_DIR, **kwargs) + + def end_headers(self): + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") + self.send_header("Access-Control-Allow-Headers", "*") + super().end_headers() + + def do_OPTIONS(self): + self.send_response(200) + self.end_headers() + + def log_message(self, format, *args): + print(f"[server] {args[0]}" if args else "") + + +if __name__ == "__main__": + server = http.server.HTTPServer(("127.0.0.1", PORT), CORSHandler) + print(f"视频服务器已启动: http://127.0.0.1:{PORT}/") + for f in Path(SERVE_DIR).glob("*.mp4"): + print(f" 可访问: http://127.0.0.1:{PORT}/{f.name}") + try: + server.serve_forever() + except KeyboardInterrupt: + server.server_close() diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/playwright_publish.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/playwright_publish.py new file mode 100644 index 00000000..baa818c1 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/playwright_publish.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +""" +使用 Playwright 全自动上传视频到抖音创作者中心。 +通过注入 cookies 实现免登录,使用 set_input_files 上传视频文件, +填写标题后自动点击发布。 +""" +import sys +import time +from pathlib import Path +from urllib.parse import unquote + +from playwright.sync_api import sync_playwright + +COOKIE_STRING = ( + "bd_ticket_guard_client_web_domain=2; " + "passport_csrf_token=7c77660e1f88141e7e90b71d7e32ad0b; " + "passport_csrf_token_default=7c77660e1f88141e7e90b71d7e32ad0b; " + "enter_pc_once=1; " + "UIFID_TEMP=08f4fe5163774c2300555b455fb414e93ca9fbb91678792eb055c0a8974f001d75b51090fcb52c4aaaf9073dc832c6dd4dc3de49b908072111108b86524fe9c74b5a387ca37e4f4839735270d224cafe; " + "gfkadpd=2906,33638; " + "csrf_session_id=6b09d5b10c4ab498c588892c745a109c; " + "biz_trace_id=45d6e984; " + "_bd_ticket_crypt_doamin=2; " + "_bd_ticket_crypt_cookie=7356de5b4441a9b6a8bdebc21d2974e9; " + "__security_mc_1_s_sdk_sign_data_key_web_protect=dfc9fff6-410f-8e60; " + "__security_mc_1_s_sdk_cert_key=e39ec87f-4583-ad3e; " + "__security_mc_1_s_sdk_crypt_sdk=3aeacc1d-4bd6-aa35; " + "__security_server_data_status=1; " + "x-web-secsdk-uid=13353cdf-41f2-4dd2-bbc5-ddf08884fd66; " + "passport_fe_beating_status=true; " + "passport_assist_user=CjzV-fLLRY-_ejIbeLJs90LfRNLuZvbI0xfqJF0GcZOZpX0hTcWo0kM00noByoHqEe1tfarXTWR6xuZvnIAaSgo8AAAAAAAAAAAAAFApNKm_uePs4rUqvWqP9f6CCWCAV30CZ9yI7jKc732ca5xgSRkDKFG6MrndTOgNN-kcEITRiw4Yia_WVCABIgEDRk9Xvg%3D%3D; " + "bd_ticket_guard_client_data_v2=eyJyZWVfcHVibGljX2tleSI6IkJJdjZnajZiZjBpM29qRW5teWZwOGhqS1ZzRUU4a0lBK1JhR00ycTg4alRQdCtkdkNwcllYNytWb3VJa0k1R2ZWNzJhNDFqNERHZmZ1ZjVmdzhWSnhmQT0iLCJyZXFfY29udGVudCI6InNlY190cyIsInJlcV9zaWduIjoieG1HQ0taTmU5U2dlNkdqaDBlTDE0UFVOUDlYbktOeDhlTzZXUkh5dnZYQT0iLCJzZWNfdHMiOiIjUFlpN0lMZTdjRERuK09KSk02V05HNDErdFJqT1RaSEVnUkxqWkZWY0ZibGdFNWVFem51bm43bkZZQUJ6In0%3D; " + "bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWl0ZXJhdGlvbi12ZXJzaW9uIjoxLCJiZC10aWNrZXQtZ3VhcmQtcmVlLXB1YmxpYy1rZXkiOiJCSXY2Z2o2YmYwaTNvakVubXlmcDhoaktWc0VFOGtJQStSYUdNMnE4OGpUUHQrZHZDcHJZWDcrVm91SWtJNUdmVjcyYTQxajRER2ZmdWY1Znc4Vkp4ZkE9IiwiYmQtdGlja2V0LWd1YXJkLXdlYi12ZXJzaW9uIjoyfQ%3D%3D" +) + +VIDEOS = [ + { + "path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/早起不是为了开派对,是不吵老婆睡觉.mp4", + "title": "早起不是为了开派对,是不吵老婆睡觉。初衷就这一个。#Soul派对 #创业日记 #晨间直播 #私域干货", + }, + { + "path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/懒人的活法 动作简单有利可图正反馈.mp4", + "title": "懒有懒的活法:动作简单、有利可图、正反馈,就能坐得住。#Soul派对 #副业 #私域 #切片变现", + }, +] + + +def parse_cookies(cookie_str: str, domain: str = ".douyin.com"): + cookies = [] + for pair in cookie_str.split("; "): + if "=" not in pair: + continue + name, value = pair.split("=", 1) + cookies.append({ + "name": name.strip(), + "value": unquote(value.strip()), + "domain": domain, + "path": "/", + }) + return cookies + + +def publish_one(context, video_info: dict, idx: int): + path = video_info["path"] + title = video_info["title"] + if not Path(path).exists(): + print(f" [{idx}] 文件不存在: {path}") + return False + + page = context.new_page() + page.goto("https://creator.douyin.com/creator-micro/content/upload", wait_until="networkidle", timeout=30000) + time.sleep(3) + + file_input = page.query_selector('input[type="file"]') + if not file_input: + file_inputs = page.query_selector_all("input") + for inp in file_inputs: + if inp.get_attribute("accept") and "video" in (inp.get_attribute("accept") or ""): + file_input = inp + break + if not file_input: + print(f" [{idx}] 未找到 file input,尝试通过 CSS 选择器") + file_input = page.query_selector('input[accept*="video"]') + + if file_input: + file_input.set_input_files(path) + print(f" [{idx}] 已选择文件: {Path(path).name}") + else: + print(f" [{idx}] 无法找到上传 input") + page.close() + return False + + print(f" [{idx}] 等待上传完成...") + for _ in range(120): + time.sleep(2) + progress_text = page.text_content("body") or "" + if "上传完成" in progress_text or "重新上传" in progress_text: + print(f" [{idx}] 上传完成") + break + if "发布" in progress_text and "上传" not in progress_text: + break + else: + print(f" [{idx}] 上传超时,继续尝试填写标题") + + time.sleep(2) + + title_editor = page.query_selector('[class*="title"] [contenteditable="true"]') + if not title_editor: + title_editor = page.query_selector('[data-placeholder*="标题"]') + if not title_editor: + title_editor = page.query_selector('.ql-editor') + if not title_editor: + title_editor = page.query_selector('[contenteditable="true"]') + + if title_editor: + title_editor.click() + title_editor.fill("") + page.keyboard.type(title, delay=20) + print(f" [{idx}] 已填写标题") + else: + print(f" [{idx}] 未找到标题输入框,跳过标题") + + time.sleep(2) + + publish_btn = page.query_selector('button:has-text("发布")') + if not publish_btn: + for btn in page.query_selector_all("button"): + txt = btn.text_content() or "" + if "发布" in txt and "定时" not in txt: + publish_btn = btn + break + + if publish_btn: + publish_btn.click() + print(f" [{idx}] 已点击发布") + time.sleep(5) + current_url = page.url + body_text = page.text_content("body") or "" + if "发布成功" in body_text or "content/manage" in current_url or "manage" in current_url: + print(f" [{idx}] 发布成功!") + page.close() + return True + else: + print(f" [{idx}] 发布状态未确认,当前URL: {current_url}") + page.close() + return True + else: + print(f" [{idx}] 未找到发布按钮") + page.close() + return False + + +def main(): + print("启动 Playwright 全自动上传到抖音创作者中心...") + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + context = browser.new_context( + viewport={"width": 1280, "height": 800}, + user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", + ) + + cookies = parse_cookies(COOKIE_STRING) + context.add_cookies(cookies) + print("已注入 cookies") + + ok, fail = 0, 0 + for i, v in enumerate(VIDEOS, 1): + print(f"\n--- 视频 {i}/{len(VIDEOS)}: {Path(v['path']).name} ---") + if publish_one(context, v, i): + ok += 1 + else: + fail += 1 + + browser.close() + print(f"\n完成: 成功 {ok},失败 {fail}") + return 0 if fail == 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/pre_publish_1_attempt0.png b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/pre_publish_1_attempt0.png new file mode 100644 index 00000000..af5f98e2 Binary files /dev/null and b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/pre_publish_1_attempt0.png differ diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/pre_publish_1_attempt1.png b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/pre_publish_1_attempt1.png new file mode 100644 index 00000000..d3c3adb0 Binary files /dev/null and b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/pre_publish_1_attempt1.png differ diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/pre_publish_1_attempt2.png b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/pre_publish_1_attempt2.png new file mode 100644 index 00000000..bf17e418 Binary files /dev/null and b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/pre_publish_1_attempt2.png differ diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/probe_publish_page.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/probe_publish_page.py new file mode 100644 index 00000000..d5bfd25b --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/probe_publish_page.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""探测抖音发布页面结构 - 找到正确的选择器""" +import asyncio +import sys +from pathlib import Path + +WANTUI_BACKEND = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend") +sys.path.insert(0, str(WANTUI_BACKEND)) + +from playwright.async_api import async_playwright +from utils.base_social_media import set_init_script + +COOKIE_FILE = Path(__file__).parent / "douyin_storage_state.json" +VIDEO = "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/睡眠不好?每天放下一件事,做减法.mp4" + +async def main(): + async with async_playwright() as pw: + browser = await pw.chromium.launch(headless=False) + context = await browser.new_context(storage_state=str(COOKIE_FILE)) + context = await set_init_script(context) + page = await context.new_page() + + await page.goto("https://creator.douyin.com/creator-micro/content/upload", + wait_until="domcontentloaded", timeout=60000) + await page.wait_for_url("**/creator.douyin.com/**/upload**", timeout=60000) + await page.wait_for_load_state("load", timeout=20000) + await asyncio.sleep(3) + + # 上传文件 + loc = page.locator("input[type='file']").first + await loc.wait_for(state="attached", timeout=10000) + await loc.set_input_files(VIDEO, timeout=120000) + print("文件已上传") + + # 等待进入发布页 + for _ in range(60): + try: + await page.wait_for_url("**/content/publish**", timeout=2000) + break + except: + try: + await page.wait_for_url("**/content/post/video**", timeout=2000) + break + except: + await asyncio.sleep(1) + print(f"当前URL: {page.url}") + await asyncio.sleep(3) + + # 关闭弹窗 + try: + iknow = page.get_by_text("我知道了", exact=True) + if await iknow.count() and await iknow.first.is_visible(): + await iknow.first.click() + print("关闭了视频预览弹窗") + await asyncio.sleep(1) + except: + pass + + # 探测输入框 + print("\n=== 探测页面输入元素 ===") + selectors = [ + 'input[placeholder*="标题"]', + 'input[placeholder*="填写"]', + '.notranslate', + '[contenteditable="true"]', + 'textarea', + '.zone-container', + '[class*="title"] input', + '[class*="title"] [contenteditable]', + 'input.semi-input', + '.semi-input', + ] + for sel in selectors: + try: + count = await page.locator(sel).count() + if count > 0: + first = page.locator(sel).first + vis = await first.is_visible() + tag = await first.evaluate("el => el.tagName") + ph = await first.evaluate("el => el.placeholder || el.getAttribute('data-placeholder') || ''") + text = await first.evaluate("el => el.textContent?.substring(0, 50) || ''") + print(f" ✓ {sel} → count={count}, visible={vis}, tag={tag}, placeholder='{ph}', text='{text[:30]}'") + except Exception as e: + print(f" ✗ {sel} → {e}") + + # 截图 + ss = Path(__file__).parent / "probe_result.png" + await page.screenshot(path=str(ss), full_page=True) + print(f"\n截图: {ss}") + + # 获取页面 HTML 片段 + html = await page.evaluate("""() => { + const inputs = document.querySelectorAll('input, textarea, [contenteditable="true"]'); + return Array.from(inputs).map(el => ({ + tag: el.tagName, + type: el.type || '', + placeholder: el.placeholder || el.getAttribute('data-placeholder') || '', + className: el.className?.substring(0, 80) || '', + visible: el.offsetHeight > 0, + })); + }""") + print("\n=== 所有输入元素 ===") + for item in html: + print(f" {item}") + + await page.pause() + await context.storage_state(path=str(COOKIE_FILE)) + await context.close() + await browser.close() + +asyncio.run(main()) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/probe_result.png b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/probe_result.png new file mode 100644 index 00000000..170b9bf5 Binary files /dev/null and b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/probe_result.png differ diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/publish_via_wantui_cookie.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/publish_via_wantui_cookie.py new file mode 100644 index 00000000..b1fd4666 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/publish_via_wantui_cookie.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +使用「万推」的腕推方案 + 浏览器 Cookie,在命令行发布本地视频到抖音创作者中心。 + +设计目标: +- 不再依赖抖音开放平台 OAuth,而是复用万推中已经验证过的 Cookie 发布逻辑。 +- 作为「抖音发布」Skill 的第二条路径:优先用 OAuth,失败或不用时,可切到 Cookie 模式。 + +前置要求: +1. 已在万推项目中完成环境准备: + - 路径:/Users/karuo/Documents/开发/3、自营项目/万推 + - 后端依赖:`cd backend && pip install -r requirements.txt` + - 通过万推的 Cookie 工具拿到一份可用的抖音 Cookie 字符串(或 JSON), + 存到本目录下 `douyin_cookie.txt`,或者导出为浏览器的 `Cookie Editor` JSON 也可以。 +2. 当前脚本所在目录为抖音发布 Skill 的脚本目录: + /Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/抖音发布/脚本 + +使用方式(例): + + python3 publish_via_wantui_cookie.py \\ + --video "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/早起不是为了开派对,是不吵老婆睡觉.mp4" \\ + --title "早起不是为了开派对,是不吵老婆睡觉。初衷就这一个。#Soul派对 #创业日记 #晨间直播 #私域干货" \\ + --tags "Soul派对,创业日记,晨间直播,私域干货" + +Cookie 读取优先级: +1) 环境变量 DOUYIN_BROWSER_COOKIE +2) 当前目录 douyin_cookie.txt +""" + +import argparse +import os +import sys +from pathlib import Path +from typing import List + + +ROOT_WANTUI_BACKEND = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend") + + +def ensure_import_path() -> None: + """将万推 backend 加入 sys.path,方便直接复用 social_publisher 逻辑。""" + backend_path = str(ROOT_WANTUI_BACKEND) + if backend_path not in sys.path: + sys.path.insert(0, backend_path) + + +def load_cookie_text() -> str: + """从环境变量或本地文件加载抖音 Cookie 字符串。""" + env_cookie = os.environ.get("DOUYIN_BROWSER_COOKIE") + if env_cookie: + return env_cookie.strip() + + cookie_file = Path(__file__).parent / "douyin_cookie.txt" + if cookie_file.exists(): + return cookie_file.read_text(encoding="utf-8").strip() + + raise SystemExit( + "未找到抖音浏览器 Cookie。\n" + "请先:\n" + "1) 将浏览器中的抖音 Cookie 复制为一整行 `key=value; key2=value2; ...`,写入本目录 douyin_cookie.txt;\n" + " 或者:\n" + "2) 导出为 JSON(Cookie Editor 导出的数组),同样写入 douyin_cookie.txt;\n" + " 或设置环境变量 DOUYIN_BROWSER_COOKIE。\n" + ) + + +def parse_tags(s: str | None) -> List[str]: + if not s: + return [] + # 支持逗号或空格分隔 + raw = [p.strip() for p in s.replace(",", ",").split(",") if p.strip()] + tags: List[str] = [] + for item in raw: + if item.startswith("#"): + item = item.lstrip("#").strip() + if item: + tags.append(item) + return tags + + +async def publish_once( + video_path: str, + title: str, + desc: str, + tags_str: str | None, +) -> int: + """调用万推的 publish_video_with_cookie,在抖音创作者中心发布一条视频。""" + ensure_import_path() + + try: + from social_publisher import publish_video_with_cookie + except Exception as e: # pragma: no cover - 导入错误直接终止 + raise SystemExit( + f"无法导入万推 backend.social_publisher:{e}\n" + f"请确认路径是否存在:{ROOT_WANTUI_BACKEND}" + ) + + cookie_text = load_cookie_text() + tags = parse_tags(tags_str) + + result = await publish_video_with_cookie( + platform="douyin", + video_path=video_path, + title=title, + cookies=cookie_text, + tags=tags, + description=desc or "", + ) + + if not result.get("success"): + err = result.get("error") or "unknown_error" + print(f"发布失败:{err}", file=sys.stderr) + return 1 + + print("发布成功:", result.get("message", "抖音创作者中心已创建任务")) + if result.get("platform_url"): + print("作品链接:", result["platform_url"]) + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser( + description="使用万推腕推方案 + 浏览器 Cookie,将本地视频发布到抖音创作者中心" + ) + parser.add_argument("--video", required=True, help="本地视频文件路径(.mp4)") + parser.add_argument("--title", required=True, help="视频标题") + parser.add_argument( + "--desc", + default="", + help="视频描述/正文(可选)", + ) + parser.add_argument( + "--tags", + default="", + help="话题标签列表,用英文逗号分隔,例如:'Soul派对,创业日记,晨间直播'", + ) + + args = parser.parse_args() + + video_path = os.path.expanduser(args.video) + if not os.path.isfile(video_path): + print(f"视频文件不存在:{video_path}", file=sys.stderr) + return 1 + + import asyncio + + return asyncio.run( + publish_once( + video_path=video_path, + title=args.title, + desc=args.desc, + tags_str=args.tags, + ) + ) + + +if __name__ == "__main__": + raise SystemExit(main()) + diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_create_api.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_create_api.py new file mode 100644 index 00000000..9f75d56e --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_create_api.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""测试抖音发布接口 - 详细调试""" +import asyncio, json, datetime, random, string, sys, hashlib, hmac, zlib, uuid +from pathlib import Path +import httpx + +sys.path.insert(0, str(Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend"))) + +COOKIE_FILE = Path(__file__).parent / "douyin_storage_state.json" +BASE = "https://creator.douyin.com" +UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" +VIDEO = "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/睡眠不好?每天放下一件事,做减法.mp4" + +def load_cookie(path): + with open(path) as f: + state = json.load(f) + return "; ".join(f"{c['name']}={c['value']}" for c in state.get("cookies", []) if "douyin.com" in c.get("domain", "")) + +def random_s(n=11): + return "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(n)) + +def _hmac(key, msg): + return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() + +def aws4_sign(ak_id, sk, token, region, service, qs): + now = datetime.datetime.now(datetime.timezone.utc) + amz = now.strftime("%Y%m%dT%H%M%SZ") + ds = now.strftime("%Y%m%d") + ch = f"x-amz-date:{amz}\nx-amz-security-token:{token}\n" + cr = f"GET\n/\n{qs}\n{ch}\nx-amz-date;x-amz-security-token\n{hashlib.sha256(b'').hexdigest()}" + scope = f"{ds}/{region}/{service}/aws4_request" + sts = f"AWS4-HMAC-SHA256\n{amz}\n{scope}\n{hashlib.sha256(cr.encode()).hexdigest()}" + k = _hmac(_hmac(_hmac(_hmac(("AWS4"+sk).encode(), ds), region), service), "aws4_request") + sig = hmac.new(k, sts.encode(), hashlib.sha256).hexdigest() + auth = f"AWS4-HMAC-SHA256 Credential={ak_id}/{ds}/{region}/{service}/aws4_request, SignedHeaders=x-amz-date;x-amz-security-token, Signature={sig}" + return auth, amz, token + +CHUNK = 3*1024*1024 + +async def main(): + cookie = load_cookie(COOKIE_FILE) + print(f"Cookie: {len(cookie)} chars") + + async with httpx.AsyncClient(timeout=120.0, follow_redirects=True) as c: + # 验证 + r = await c.get(f"{BASE}/web/api/media/user/info/", headers={"Cookie": cookie, "User-Agent": UA}) + d = r.json() + print(f"用户: {d.get('user_info',{}).get('nickname','?')} (status={d.get('status_code')})") + if d.get("status_code") != 0: + print("Cookie无效!"); return + + # Step 1: auth + r = await c.get(f"{BASE}/web/api/media/upload/auth/v5/", headers={"Cookie": cookie, "User-Agent": UA}) + auth_data = r.json() + print(f"Auth: status={auth_data.get('status_code')}") + ak = auth_data["ak"] + cred = json.loads(auth_data["auth"]) + + # Step 2: apply upload + fs = Path(VIDEO).stat().st_size + params = {"Action":"ApplyUploadInner","FileSize":str(fs),"FileType":"video","IsInner":"1","SpaceName":"aweme","Version":"2020-11-19","app_id":"2906","s":random_s(),"user_id":""} + qs = "&".join(f"{k}={v}" for k,v in sorted(params.items())) + authorization, amz_date, st = aws4_sign(cred["AccessKeyID"], cred["SecretAccessKey"], cred["SessionToken"], "cn-north-1", "vod", qs) + r = await c.get("https://imagex.bytedanceapi.com/", params=params, headers={"authorization":authorization,"x-amz-date":amz_date,"x-amz-security-token":st,"User-Agent":UA}) + apply = r.json() + result = apply.get("Result", {}) + + # 解析上传地址 + inner = result.get("InnerUploadAddress", result) + nodes = inner.get("UploadNodes", [{}]) + host = nodes[0].get("UploadHost", "") if nodes else "" + stores = nodes[0].get("StoreInfos", [{}]) if nodes else [{}] + uri = stores[0].get("StoreUri", "") + store_auth = stores[0].get("Auth", "") + session_key = inner.get("SessionKey", "") + + if not host: + addr = result.get("UploadAddress", {}) + host = addr.get("UploadHosts", [""])[0] + stores = addr.get("StoreInfos", [{}]) + uri = stores[0].get("StoreUri", "") + store_auth = stores[0].get("Auth", "") + session_key = addr.get("SessionKey", "") + + print(f"Upload: host={host}, uri={uri[:40]}...") + + # Step 3: upload chunks + data = Path(VIDEO).read_bytes() + total = (len(data) + CHUNK - 1) // CHUNK + uid = str(uuid.uuid4()) + base_url = f"https://{host}/upload/v1/{uri}" + + for i in range(total): + s, e = i*CHUNK, min((i+1)*CHUNK, len(data)) + chunk = data[s:e] + crc = hex(zlib.crc32(chunk) & 0xFFFFFFFF)[2:] + r = await c.post(base_url, params={"uploadid":uid,"part_number":str(i+1),"part_offset":str(s),"phase":"transfer"}, content=chunk, headers={"Authorization":store_auth,"Content-CRC32":crc,"Content-Type":"application/octet-stream","User-Agent":UA}) + rd = r.json() + print(f" chunk {i+1}/{total}: code={rd.get('code')}") + + # Step 4: 测试多种 create 请求格式 + csrf = "" + for part in cookie.split(";"): + kv = part.strip().split("=", 1) + if len(kv)==2 and kv[0].strip()=="passport_csrf_token": + csrf = kv[1].strip() + + title = "睡眠不好?每天放下一件事,做减法。#睡眠 #减法 #Soul派对 #生活方式" + creation_id = f"{random_s(8)}{int(datetime.datetime.now().timestamp()*1000)}" + + # 尝试方式 A: text/plain + form body + body_a = f"text={title}&text_extra=[]&activity=[]&challenges=[]&hashtag_source=&mentions=[]&ifLongTitle=true&hot_sentence=&visibility_type=0&download=1&poster={uri}&timing=-1&video={{\"uri\":\"{uri}\"}}&creation_id={creation_id}" + + headers_base = { + "Cookie": cookie, + "User-Agent": UA, + "Referer": "https://creator.douyin.com/creator-micro/content/publish", + "Origin": "https://creator.douyin.com", + "Accept": "application/json, text/plain, */*", + } + if csrf: + headers_base["X-CSRFToken"] = csrf + + print("\n=== 方式A: text/plain ===") + h = {**headers_base, "Content-Type": "text/plain"} + r = await c.post(f"{BASE}/web/api/media/aweme/create/", headers=h, content=body_a) + print(f" Status: {r.status_code}") + print(f" Headers: {dict(r.headers)}") + print(f" Body len: {len(r.content)}") + print(f" Body: {r.text[:500]}") + + # 尝试方式 B: application/x-www-form-urlencoded + print("\n=== 方式B: form-urlencoded ===") + form_data = { + "text": title, + "text_extra": "[]", + "activity": "[]", + "challenges": "[]", + "hashtag_source": "", + "mentions": "[]", + "ifLongTitle": "true", + "hot_sentence": "", + "visibility_type": "0", + "download": "1", + "poster": uri, + "timing": "-1", + "video": json.dumps({"uri": uri}), + "creation_id": creation_id, + } + h = {**headers_base, "Content-Type": "application/x-www-form-urlencoded"} + r = await c.post(f"{BASE}/web/api/media/aweme/create/", headers=h, data=form_data) + print(f" Status: {r.status_code}") + print(f" Headers: {dict(r.headers)}") + print(f" Body len: {len(r.content)}") + print(f" Body: {r.text[:500]}") + + # 尝试方式 C: application/json + print("\n=== 方式C: application/json ===") + h = {**headers_base, "Content-Type": "application/json"} + r = await c.post(f"{BASE}/web/api/media/aweme/create/", headers=h, json=form_data) + print(f" Status: {r.status_code}") + print(f" Headers: {dict(r.headers)}") + print(f" Body len: {len(r.content)}") + print(f" Body: {r.text[:500]}") + + # 尝试方式 D: 不同的 URL (post instead of create) + print("\n=== 方式D: /aweme/v1/web/aweme/post/ ===") + r = await c.post(f"{BASE}/aweme/v1/web/aweme/post/", headers={**headers_base, "Content-Type": "application/json"}, json=form_data) + print(f" Status: {r.status_code}") + print(f" Body len: {len(r.content)}") + print(f" Body: {r.text[:500]}") + +asyncio.run(main()) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_create_v2.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_create_v2.py new file mode 100644 index 00000000..7668ccda --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_create_v2.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +""" +测试 create_v2 端点 — 逐步测试不同参数组合。 +先用已有的 video_id(已上传成功的视频)测试发布。 +""" +import asyncio +import json +import random +import string +import time +from pathlib import Path +from urllib.parse import urlencode + +import httpx + +SCRIPT_DIR = Path(__file__).parent +COOKIE_FILE = SCRIPT_DIR / "douyin_storage_state.json" + +UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" +BASE_URL = "https://creator.douyin.com" + + +def extract_cookies_string(storage_state_path: str) -> str: + with open(storage_state_path) as f: + data = json.load(f) + cookies = data.get("cookies", []) + parts = [] + seen = set() + for c in cookies: + key = c["name"] + if key not in seen: + parts.append(f"{key}={c['value']}") + seen.add(key) + return "; ".join(parts) + + +def extract_csrf(cookies_str: str) -> str: + for part in cookies_str.split(";"): + part = part.strip() + if part.startswith("passport_csrf_token="): + return part.split("=", 1)[1] + return "" + + +def random_creation_id() -> str: + chars = string.ascii_lowercase + string.digits + prefix = "".join(random.choices(chars, k=8)) + ts = str(int(time.time() * 1000)) + return prefix + ts + + +def build_query_params(ms_token: str = "") -> dict: + return { + "read_aid": "2906", + "cookie_enabled": "true", + "screen_width": "1280", + "screen_height": "720", + "browser_language": "zh-CN", + "browser_platform": "MacIntel", + "browser_name": "Mozilla", + "browser_version": UA, + "browser_online": "true", + "timezone_name": "Asia/Shanghai", + "aid": "1128", + "support_h265": "1", + } + + +def build_body(video_id: str, title: str, poster_uri: str = "", timing: int = 0) -> dict: + return { + "item": { + "common": { + "text": title, + "caption": title, + "item_title": "", + "activity": "[]", + "text_extra": "[]", + "challenges": "[]", + "mentions": "[]", + "hashtag_source": "", + "hot_sentence": "", + "interaction_stickers": "[]", + "visibility_type": 0, + "download": 1, + "timing": timing, + "creation_id": random_creation_id(), + "media_type": 4, + "video_id": video_id, + "music_source": 0, + "music_id": None, + }, + "cover": { + "custom_cover_image_height": 0, + "custom_cover_image_width": 0, + "poster": poster_uri, + "poster_delay": 0, + }, + } + } + + +async def test_create(video_id: str, title: str): + cookie_str = extract_cookies_string(str(COOKIE_FILE)) + csrf = extract_csrf(cookie_str) + + print(f"Cookie 长度: {len(cookie_str)}") + print(f"CSRF token: {csrf[:20]}...") + + headers = { + "Cookie": cookie_str, + "User-Agent": UA, + "Content-Type": "application/json", + "Accept": "application/json, text/plain, */*", + "Referer": "https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page", + "Origin": "https://creator.douyin.com", + } + if csrf: + headers["x-secsdk-csrf-token"] = csrf + + body = build_body(video_id, title) + body_json = json.dumps(body, ensure_ascii=False) + + # Test 1: 无 msToken 无 a_bogus + print("\n" + "="*60) + print(" 测试1: 无 msToken, 无 a_bogus") + print("="*60) + params = build_query_params() + url = f"{BASE_URL}/web/api/media/aweme/create_v2/?{urlencode(params)}" + async with httpx.AsyncClient(timeout=30.0) as client: + resp = await client.post(url, headers=headers, content=body_json) + print(f" HTTP {resp.status_code}") + print(f" Headers: {dict(resp.headers)}") + text = resp.text + print(f" Body ({len(text)} chars): {text[:500]}") + + # Test 2: 带随机 msToken + print("\n" + "="*60) + print(" 测试2: 随机 msToken, 无 a_bogus") + print("="*60) + fake_ms = "".join(random.choices(string.ascii_letters + string.digits + "_-", k=128)) + "==" + params2 = build_query_params() + params2["msToken"] = fake_ms + url2 = f"{BASE_URL}/web/api/media/aweme/create_v2/?{urlencode(params2)}" + async with httpx.AsyncClient(timeout=30.0) as client: + resp2 = await client.post(url2, headers=headers, content=body_json) + print(f" HTTP {resp2.status_code}") + text2 = resp2.text + print(f" Body ({len(text2)} chars): {text2[:500]}") + + # Test 3: 带 x-secsdk-csrf-token 值用 csrf_session_id + print("\n" + "="*60) + print(" 测试3: csrf_session_id 作为 CSRF, 无 a_bogus") + print("="*60) + headers3 = headers.copy() + for part in cookie_str.split(";"): + part = part.strip() + if part.startswith("csrf_session_id="): + headers3["x-secsdk-csrf-token"] = part.split("=", 1)[1] + async with httpx.AsyncClient(timeout=30.0) as client: + resp3 = await client.post(url2, headers=headers3, content=body_json) + print(f" HTTP {resp3.status_code}") + text3 = resp3.text + print(f" Body ({len(text3)} chars): {text3[:500]}") + + +async def main(): + # 使用之前拦截到的已上传 video_id + video_id = "v0200fg10000d6nbfknog65sq49b99gg" + title = "广点通能投Soul了 纯API测试" + + print(f"video_id: {video_id}") + print(f"title: {title}") + + await test_create(video_id, title) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_headless_create.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_headless_create.py new file mode 100644 index 00000000..cc488f84 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_headless_create.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +""" +测试:headless Playwright page.evaluate(fetch) 调用 create_v2 +验证浏览器 JS 环境是否自动添加 a_bogus/msToken + bd-ticket-guard +""" +import asyncio +import json +import os +import random +import string +import time +from pathlib import Path + +from playwright.async_api import async_playwright + +SCRIPT_DIR = Path(__file__).parent +COOKIE_FILE = SCRIPT_DIR / "douyin_storage_state.json" +STEALTH_JS = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend/utils/stealth.min.js") +CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + +TEST_VIDEO_ID = "v0200fg10000d6nbfknog65sq49b99gg" +TEST_POSTER = "tos-cn-i-jm8ajry58r/72772c27a4b648b4a5d3e3e074d81d55" + + +def random_creation_id() -> str: + chars = string.ascii_lowercase + string.digits + prefix = "".join(random.choices(chars, k=8)) + ts = str(int(time.time() * 1000)) + return prefix + ts + + +async def main(): + if not COOKIE_FILE.exists(): + print("[!] Cookie 不存在") + return 1 + + print("[i] 启动浏览器...") + async with async_playwright() as pw: + browser = await pw.chromium.launch( + headless=False, + executable_path=CHROME if os.path.exists(CHROME) else None, + ) + context = await browser.new_context(storage_state=str(COOKIE_FILE)) + if STEALTH_JS.exists(): + await context.add_init_script(path=str(STEALTH_JS)) + + page = await context.new_page() + + def on_request(request): + if "create_v2" in request.url: + print(f"\n[NET] create_v2 请求:") + print(f" URL 前200: {request.url[:200]}") + print(f" a_bogus: {'✓' if 'a_bogus' in request.url else '✗'}") + print(f" msToken: {'✓' if 'msToken' in request.url else '✗'}") + hdrs = dict(request.headers) + for key in ["x-secsdk-csrf-token", "bd-ticket-guard-client-data", "bd-ticket-guard-web-version", "x-tt-session-dtrait"]: + print(f" {key}: {'✓' if key in hdrs else '✗'}") + + def on_response(response): + if "create_v2" in response.url: + print(f"[NET] create_v2 响应: HTTP {response.status}") + + page.on("request", on_request) + page.on("response", on_response) + + # 打开发布页(让 JS 安全 SDK 完全加载) + print("[1] 打开发布页...") + await page.goto("https://creator.douyin.com/creator-micro/content/upload", + wait_until="domcontentloaded", timeout=60000) + await page.wait_for_load_state("networkidle", timeout=30000) + + if await page.get_by_text("手机号登录").count() or await page.get_by_text("扫码登录").count(): + print("[!] Cookie 失效") + await browser.close() + return 1 + print("[1] OK") + await asyncio.sleep(5) + + creation_id = random_creation_id() + body = { + "item": { + "common": { + "text": "headless API 测试发布 v2", + "caption": "headless API 测试发布 v2", + "item_title": "", + "activity": "[]", + "text_extra": "[]", + "challenges": "[]", + "mentions": "[]", + "hashtag_source": "", + "hot_sentence": "", + "interaction_stickers": "[]", + "visibility_type": 0, + "download": 1, + "timing": 0, + "creation_id": creation_id, + "media_type": 4, + "video_id": TEST_VIDEO_ID, + "music_source": 0, + "music_id": None, + }, + "cover": { + "custom_cover_image_height": 0, + "custom_cover_image_width": 0, + "poster": TEST_POSTER, + "poster_delay": 0, + }, + } + } + body_json = json.dumps(body, ensure_ascii=False) + + # 方案 A: 使用 XHR (可能有不同的拦截器) + print("\n[测试A] 使用 XMLHttpRequest...") + js_xhr = f""" + () => new Promise((resolve) => {{ + const xhr = new XMLHttpRequest(); + xhr.open('POST', '/web/api/media/aweme/create_v2/'); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.setRequestHeader('Accept', 'application/json, text/plain, */*'); + xhr.withCredentials = true; + xhr.onload = function() {{ + resolve({{ + status: xhr.status, + url: xhr.responseURL, + body: xhr.responseText.substring(0, 2000), + headers: xhr.getAllResponseHeaders(), + }}); + }}; + xhr.onerror = function() {{ resolve({{ error: 'network error' }}); }}; + xhr.send(JSON.stringify({body_json})); + }}) + """ + result_a = await page.evaluate(js_xhr) + print(f" 结果A: {json.dumps(result_a, ensure_ascii=False, indent=2)}") + + await asyncio.sleep(2) + + # 方案 B: 找页面内部的 axios/request 实例 + print("\n[测试B] 搜索页面内部 request 方法...") + js_find = """ + () => { + const found = []; + // 检查常见的 request 实例 + if (window.axios) found.push('window.axios'); + if (window.__axios) found.push('window.__axios'); + if (window._request) found.push('window._request'); + if (window.__request) found.push('window.__request'); + if (window.request) found.push('window.request'); + if (window.__NUXT__) found.push('window.__NUXT__'); + if (window.__NEXT_DATA__) found.push('window.__NEXT_DATA__'); + if (window.__APP_DATA__) found.push('window.__APP_DATA__'); + + // 检查 ByteDance SDK + if (window.byted_acrawler) found.push('window.byted_acrawler'); + if (window.bdms) found.push('window.bdms'); + if (window.__bd_ticket_guard_client) found.push('window.__bd_ticket_guard_client'); + if (window._bytedGuard) found.push('window._bytedGuard'); + if (window.SSR_HYDRATED_DATA) found.push('window.SSR_HYDRATED_DATA'); + if (window.__LOADABLE_LOADED_CHUNKS__) found.push('window.__LOADABLE_LOADED_CHUNKS__'); + + // 检查 React 内部 + try { + const keys = Object.keys(window).filter(k => !k.startsWith('_') || k.startsWith('__')); + const interesting = keys.filter(k => { + try { return typeof window[k] === 'function' || (typeof window[k] === 'object' && window[k] !== null); } catch(e) { return false; } + }); + found.push('total_window_keys: ' + keys.length); + } catch(e) {} + + return found; + } + """ + found = await page.evaluate(js_find) + print(f" 找到: {found}") + + # 方案 C: fetch + credentials: include + 手动构造安全头 + print("\n[测试C] fetch + credentials include...") + js_fetch_c = f""" + async () => {{ + try {{ + // 尝试从页面获取 csrf token + const cookies = document.cookie; + let csrfToken = ''; + const match = cookies.match(/passport_csrf_token=([^;]+)/); + if (match) csrfToken = match[1]; + + // 尝试获取 x-secsdk-csrf-token + let secCsrf = ''; + try {{ + const metaTag = document.querySelector('meta[name="csrf-token"]'); + if (metaTag) secCsrf = metaTag.content; + }} catch(e) {{}} + + const resp = await fetch('/web/api/media/aweme/create_v2/', {{ + method: 'POST', + credentials: 'include', + headers: {{ + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/plain, */*', + }}, + body: JSON.stringify({body_json}), + }}); + const text = await resp.text(); + return {{ + status: resp.status, + url: resp.url, + body: text.substring(0, 2000), + csrfToken: csrfToken, + secCsrf: secCsrf, + }}; + }} catch(e) {{ + return {{ error: e.message }}; + }} + }} + """ + result_c = await page.evaluate(js_fetch_c) + print(f" 结果C: {json.dumps(result_c, ensure_ascii=False, indent=2)}") + + await asyncio.sleep(2) + await context.storage_state(path=str(COOKIE_FILE)) + await context.close() + await browser.close() + + return 0 + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_one_publish.py b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_one_publish.py new file mode 100644 index 00000000..0e1295e4 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/test_one_publish.py @@ -0,0 +1,351 @@ +#!/usr/bin/env python3 +""" +测试单条发布 — 通过 JS hook 在页面内捕获 video_id +""" +import asyncio +import json +import os +import random +import string +import sys +import time +from datetime import datetime +from pathlib import Path + +from playwright.async_api import async_playwright + +SCRIPT_DIR = Path(__file__).parent +COOKIE_FILE = SCRIPT_DIR / "douyin_storage_state.json" +STEALTH_JS = Path("/Users/karuo/Documents/开发/3、自营项目/万推/backend/utils/stealth.min.js") +CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + +VIDEO = "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/广点通能投Soul了,1000曝光6到10块.mp4" +TITLE = "广点通能投Soul了,1000曝光6到10块。#Soul派对 #广点通 #流量投放" + +# JS 注入:hook fetch/XHR 捕获 video_id 和所有关键响应 +HOOK_JS = r""" +window.__dy_video_ids = []; +window.__dy_responses = []; + +const _origFetch = window.fetch; +window.fetch = async function(...args) { + const resp = await _origFetch.apply(this, args); + const url = typeof args[0] === 'string' ? args[0] : (args[0] && args[0].url) || ''; + + if (url.includes('vod.bytedance') || url.includes('video/enable') || + url.includes('video/transend') || url.includes('video/sts') || + url.includes('ApplyUpload') || url.includes('CommitUpload')) { + try { + const clone = resp.clone(); + const text = await clone.text(); + window.__dy_responses.push({ url: url.substring(0, 200), body: text.substring(0, 2000) }); + + // 搜索 video_id + const vidMatch = text.match(/"(?:video_id|VideoId|vid)":\s*"(v0[a-zA-Z0-9]+)"/); + if (vidMatch) { + window.__dy_video_ids.push(vidMatch[1]); + console.log('[HOOK] video_id found: ' + vidMatch[1]); + } + } catch(e) {} + } + return resp; +}; + +// Also hook XHR +const _xhrOpen = XMLHttpRequest.prototype.open; +const _xhrSend = XMLHttpRequest.prototype.send; +XMLHttpRequest.prototype.open = function(method, url, ...rest) { + this.__hookUrl = url; + return _xhrOpen.apply(this, [method, url, ...rest]); +}; +XMLHttpRequest.prototype.send = function(body) { + const self = this; + const origHandler = self.onreadystatechange; + self.onreadystatechange = function() { + if (self.readyState === 4 && self.__hookUrl) { + const url = self.__hookUrl; + if (url.includes('vod.bytedance') || url.includes('video/enable') || + url.includes('video/transend') || url.includes('video/sts') || + url.includes('ApplyUpload') || url.includes('CommitUpload')) { + try { + const text = self.responseText; + window.__dy_responses.push({ url: url.substring(0, 200), body: text.substring(0, 2000), type: 'xhr' }); + const vidMatch = text.match(/"(?:video_id|VideoId|vid)":\s*"(v0[a-zA-Z0-9]+)"/); + if (vidMatch) { + window.__dy_video_ids.push(vidMatch[1]); + console.log('[HOOK-XHR] video_id found: ' + vidMatch[1]); + } + } catch(e) {} + } + } + if (origHandler) origHandler.apply(self, arguments); + }; + return _xhrSend.apply(this, [body]); +}; + +console.log('[HOOK] fetch/XHR hook installed for video_id capture'); +""" + + +def random_creation_id() -> str: + chars = string.ascii_lowercase + string.digits + return "".join(random.choices(chars, k=8)) + str(int(time.time() * 1000)) + + +async def main(): + if not COOKIE_FILE.exists(): + print("[!] Cookie 不存在") + return 1 + + timing_ts = int(time.time()) + 2 * 3600 + timing_ts = (timing_ts // 3600) * 3600 + print(f"视频: {Path(VIDEO).name}") + print(f"标题: {TITLE}") + print(f"定时: {datetime.fromtimestamp(timing_ts).strftime('%Y-%m-%d %H:%M')}") + + async with async_playwright() as pw: + browser = await pw.chromium.launch( + headless=False, + executable_path=CHROME if os.path.exists(CHROME) else None, + ) + context = await browser.new_context(storage_state=str(COOKIE_FILE)) + if STEALTH_JS.exists(): + await context.add_init_script(path=str(STEALTH_JS)) + # 注入 hook 到所有新页面 + await context.add_init_script(script=HOOK_JS) + + page = await context.new_page() + page.on("console", lambda msg: print(f" [console] {msg.text}") if "HOOK" in msg.text else None) + + # 1. 打开上传页 + print("\n[1] 打开上传页...") + await page.goto("https://creator.douyin.com/creator-micro/content/upload", + wait_until="domcontentloaded", timeout=60000) + await page.wait_for_url("**/upload**", timeout=60000) + await page.wait_for_load_state("load", timeout=20000) + + if await page.get_by_text("手机号登录").count() or await page.get_by_text("扫码登录").count(): + print("[!] Cookie 失效") + await browser.close() + return 1 + print("[1] OK") + await asyncio.sleep(3) + + # 2. 上传 + print("[2] 上传视频...") + loc = page.locator("input[type='file']").first + await loc.wait_for(state="attached", timeout=10000) + await loc.set_input_files(VIDEO, timeout=60000) + print("[2] OK") + + # 3. 等待发布页 + for _ in range(120): + if "publish" in page.url or "post/video" in page.url: + break + await asyncio.sleep(1) + print(f"[3] 发布页就绪") + + # 再次注入 hook(页面导航可能清空了) + await page.evaluate(HOOK_JS) + await asyncio.sleep(5) + + # 4. 等待 video_id 出现 + print("[4] 等待 video_id...") + video_id = None + for i in range(180): + vids = await page.evaluate("window.__dy_video_ids || []") + if vids: + video_id = vids[-1] + print(f"[4] ✓ video_id: {video_id}") + break + + # 每 15 秒检查一次所有响应 + if i % 15 == 0 and i > 0: + resps = await page.evaluate("window.__dy_responses || []") + print(f" ...{i}s, 捕获响应: {len(resps)}, video_ids: {len(vids)}") + if resps: + for r in resps[-3:]: + print(f" {r.get('url', '')[:80]}") + + await asyncio.sleep(1) + + # 如果 hook 没捕获到,尝试从页面提取 + if not video_id: + print("[4] hook 未捕获,尝试其他方式...") + + # 方式A: 从页面中所有 img/video 元素查找 + video_id = await page.evaluate(r""" + () => { + // 搜索整个 DOM 的文本内容 + const html = document.documentElement.outerHTML; + const match = html.match(/video_id['":\s]*(v0[a-zA-Z0-9]{20,})/); + if (match) return match[1]; + + // 搜索 video/source 元素 + const videos = document.querySelectorAll('video source, video'); + for (const v of videos) { + const src = v.src || v.currentSrc || ''; + const m = src.match(/(v0[a-zA-Z0-9]{20,})/); + if (m) return m[1]; + } + return null; + } + """) + if video_id: + print(f"[4] DOM 搜索找到: {video_id}") + + if not video_id: + # 查看捕获的所有响应 + all_resps = await page.evaluate("window.__dy_responses || []") + print(f"[4] 所有捕获的响应 ({len(all_resps)}):") + for r in all_resps: + print(f" {r.get('url', '')[:100]}") + body = r.get('body', '') + if body: + print(f" body: {body[:200]}") + + # 5. 获取 poster + poster = await page.evaluate(r""" + () => { + const imgs = document.querySelectorAll('img[src*="tos-cn-i-"]'); + for (const img of imgs) { + const match = img.src.match(/(tos-cn-i-[a-zA-Z0-9]+\/[a-f0-9]+)/); + if (match) return match[1]; + } + return ''; + } + """) + print(f"[5] poster: {poster[:50] if poster else 'N/A'}") + + if video_id: + # 6. 用 fetch 发布 + print(f"\n[6] 通过 fetch 调用 create_v2...") + creation_id = random_creation_id() + body = { + "item": { + "common": { + "text": TITLE, + "caption": TITLE, + "item_title": "", + "activity": "[]", + "text_extra": "[]", + "challenges": "[]", + "mentions": "[]", + "hashtag_source": "", + "hot_sentence": "", + "interaction_stickers": "[]", + "visibility_type": 0, + "download": 1, + "timing": timing_ts, + "creation_id": creation_id, + "media_type": 4, + "video_id": video_id, + "music_source": 0, + "music_id": None, + }, + "cover": { + "custom_cover_image_height": 0, + "custom_cover_image_width": 0, + "poster": poster or "", + "poster_delay": 0, + }, + } + } + body_json = json.dumps(body, ensure_ascii=False) + + result = await page.evaluate(f""" + async () => {{ + try {{ + const resp = await fetch('/web/api/media/aweme/create_v2/', {{ + method: 'POST', + credentials: 'include', + headers: {{ + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/plain, */*', + }}, + body: JSON.stringify({body_json}), + }}); + const text = await resp.text(); + return {{ status: resp.status, body: text.substring(0, 3000) }}; + }} catch(e) {{ + return {{ error: e.message }}; + }} + }} + """) + print(f"[6] HTTP {result.get('status', '?')}") + resp_body = result.get("body", "") + print(f"[6] 响应: {resp_body[:500]}") + + if resp_body: + try: + parsed = json.loads(resp_body) + if parsed.get("status_code") == 0: + print("\n[✓] 发布成功!") + await context.storage_state(path=str(COOKIE_FILE)) + await context.close() + await browser.close() + return 0 + except Exception: + pass + + # 回退到浏览器点击发布 + print("\n[回退] 使用浏览器点击发布...") + try: + nl = page.locator(".notranslate").first + await nl.click(timeout=5000) + await page.keyboard.press("Meta+KeyA") + await page.keyboard.press("Delete") + await page.keyboard.type(TITLE[:50], delay=20) + await asyncio.sleep(2) + except Exception as e: + print(f" 标题填写异常: {e}") + + for attempt in range(8): + try: + pub = page.get_by_role("button", name="发布", exact=True) + if await pub.count(): + await pub.click() + print(f" 点击发布 (attempt {attempt+1})") + + await page.wait_for_url("**/content/manage**", timeout=10000) + print("[✓] 浏览器发布成功!") + await context.storage_state(path=str(COOKIE_FILE)) + await context.close() + await browser.close() + return 0 + except Exception: + # 处理封面 + try: + if await page.get_by_text("请设置封面后再发布").first.is_visible(): + print(f" 需要封面...") + covers = page.locator('[class*="recommendCover"], [class*="cover-select-item"]') + if await covers.count() > 0: + await covers.first.click() + await asyncio.sleep(2) + confirm = page.get_by_role("button", name="确定") + if await confirm.count(): + await confirm.click() + await asyncio.sleep(2) + continue + except Exception: + pass + + # 处理安全验证 + try: + if await page.locator('text="安全验证"').count() > 0 or await page.locator('text="身份验证"').count() > 0: + print(f" [!] 触发安全验证,等待用户手动处理...") + await asyncio.sleep(30) + except Exception: + pass + + await asyncio.sleep(3) + + print("\n结果: 失败") + await context.storage_state(path=str(COOKIE_FILE)) + await context.close() + await browser.close() + return 1 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/03_卡木(木)/木叶_视频内容/抖音发布/脚本/一键发布到抖音.sh b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/一键发布到抖音.sh new file mode 100755 index 00000000..ca3a15ee --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/抖音发布/脚本/一键发布到抖音.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# 一键发布 119 场成片到抖音。 +# 若已有 tokens.json 或 DOUYIN_ACCESS_TOKEN/DOUYIN_OPEN_ID 则直接发布; +# 若未配置 token 但设置了 DOUYIN_CLIENT_KEY/DOUYIN_CLIENT_SECRET 则先走 OAuth 再发布。 +set -e +cd "$(dirname "$0")" +if [ -f "tokens.json" ]; then + python3 batch_publish_119.py +elif [ -n "$DOUYIN_CLIENT_KEY" ] && [ -n "$DOUYIN_CLIENT_SECRET" ]; then + python3 douyin_oauth_then_publish.py +else + python3 batch_publish_119.py +fi diff --git a/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md b/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md index 9ad1c448..7189c3fa 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md +++ b/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md @@ -52,10 +52,18 @@ ## 五、成片:封面 + 字幕 + 竖屏 -- **封面**:竖屏 498×1080 内**不超出界面**;**半透明质感**(背景 alpha=165);深色渐变、左上角 Soul logo;**封面显示标题 = 成片文件名 = highlights.title**(去杠后一致,无 `:|—/`、无序号);标题文字严格居中、多行自动换行。透明度由 `VERTICAL_COVER_ALPHA` 调节。 -- **字幕**:封面结束后才显示,**居中**在竖屏内;烧录用**图像 overlay**(每张字幕图 `-loop 1` + `enable=between(t,a,b)`),若系统 FFmpeg 带 libass 可改用 SRT+subtitles 滤镜;语助词由 soul_enhance 统一清理。重新加字幕时加 `--force-burn-subs`。 +- **封面**:竖屏 498×1080 内**不超出界面**;**半透明质感**(背景 alpha=165);深色渐变、左上角 Soul logo;**封面显示标题 = 成片文件名 = highlights.title**(去杠、去下划线后一致,无 `:|—/_`、无序号);标题文字严格居中、多行自动换行。透明度由 `VERTICAL_COVER_ALPHA` 调节。 +- **字幕**:封面结束后才显示,**居中**在竖屏内;先尝试**单次 FFmpeg 通道**(一次 pass 完成所有字幕叠加,最快);若失败自动回退到分批模式(batch_size=40);语助词在解析阶段已由 `clean_filler_words` 去除。重新加字幕时加 `--force-burn-subs`。⚠️ 注意:当前 FFmpeg 不支持 drawtext/subtitles 滤镜,只能用 PIL 图像 overlay 方案。 - **竖屏**:498×1080,crop 参数与 `参考资料/竖屏中段裁剪参数说明.md` 一致 +### ⚠️ 字幕烧录常见坑(已修复) + +| 坑 | 原因 | 修复 | +|---|---|---| +| 字幕全跳过(转录稿异常误判) | `_parse_clip_index` 取到场次号(如 119)而非切片序号(01),导致 highlight_info 为空,start_sec=0 落入噪声区 | 改为取 `_数字_` 模式中**最小值**,119→01=1 ✓ | +| 标题/文件名有下划线 | `sanitize_filename` 保留了 `_` | 现在 `_` 也替换为空格 | +| 字幕烧录极慢(N/5 次 encode) | 原 batch_size=5,180 条字幕需 36 次 FFmpeg 重编码 | 改为单次通道(1 次 pass);失败时 batch_size=40 兜底 | + --- ## 六、竖屏裁剪参数(成片内嵌) @@ -80,9 +88,11 @@ python3 batch_clip.py -i "原视频.mp4" -l highlights.json -o clips/ -p soul112 **3. 成片(竖屏+封面+字幕+去语助词,直出到 成片/)** ```bash -python3 soul_enhance.py -c clips/ -l highlights.json -t transcript.srt -o 成片/ --vertical --title-only +python3 soul_enhance.py -c clips/ -l highlights.json -t transcript.srt -o 成片/ --vertical --title-only --force-burn-subs ``` +**前缀命名注意**:`-p soul119` 这类带场次号的前缀会产生 `soul119_01_xxx.mp4`,`soul_enhance` 会正确识别 `01` 为切片序号(取所有 `_数字_` 中最小值)。 + 输出目录结构示例: ``` xxx_output/ diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/batch_clip.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/batch_clip.py index 7c00087b..8f156500 100755 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/batch_clip.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/batch_clip.py @@ -71,16 +71,21 @@ def _title_no_slash(s: str) -> str: return s -def sanitize_filename(name: str, max_length: int = 50, chinese_only: bool = True) -> str: - """清理文件名,先标题去杠,再仅保留中文、空格、_-""" +_SAFE_CJK_PUNCT = set(",。?!;:·、…()【】「」《》~—·+") + + +def sanitize_filename(name: str, max_length: int = 50, chinese_only: bool = False) -> str: + """清理文件名:去杠去下划线,保留中文、ASCII字母数字(MBTI/AI/ENFJ等)、安全标点与空格""" name = _title_no_slash(name) or _to_simplified(str(name)) safe_chars = [] for c in name: - if c in " _-" or "\u4e00" <= c <= "\u9fff": - safe_chars.append(c) - elif not chinese_only and (c.isalnum() or c.isdigit()): + if (c == " " + or "\u4e00" <= c <= "\u9fff" + or c.isalnum() + or c in _SAFE_CJK_PUNCT): safe_chars.append(c) result = "".join(safe_chars).strip() + result = __import__('re').sub(r"\s+", " ", result).strip() if len(result) > max_length: result = result[:max_length] return result.strip(" _-") or "片段" diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py index 3bb1fc00..0a995923 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py @@ -184,28 +184,38 @@ def draw_text_with_outline(draw, pos, text, font, color, outline_color, outline_ draw.text((x, y), text, font=font, fill=color) def _normalize_title_for_display(title: str) -> str: - """标题去杠、更清晰:将 :|、—、/ 等替换为空格""" + """标题去杠去下划线:将 :|、—、/、_ 等全部替换为空格,避免文件名和封面出现杂符号""" if not title: return "" s = _to_simplified(str(title).strip()) - for char in "::||—--/、": + for char in "::||—--/、_": s = s.replace(char, " ") s = re.sub(r"\s+", " ", s).strip() return s +# macOS/APFS 文件名允许的中文标点(保留刺激性标题所需的标点) +_SAFE_CJK_PUNCT = set(",。?!;:·、…()【】「」《》~—·+") + def sanitize_filename(name: str, max_length: int = 50) -> str: - """成片文件名:先标题去杠,再仅保留中文、空格、_-""" + """成片文件名:先去杠去下划线,再保留中文、ASCII字母数字、安全标点与空格。 + + 保留英文大写(如 MBTI、ENFJ)和数字(如 170万、1000曝光),避免因过度过滤 + 导致标题残缺(如原来 ENFJ 等被删掉变成 '组建团队 初期找')。 + """ name = _normalize_title_for_display(name) or _to_simplified(str(name)) safe = [] for c in name: - if c in " _-" or "\u4e00" <= c <= "\u9fff": + if (c == " " + or "\u4e00" <= c <= "\u9fff" # 中文字符 + or c.isalnum() # ASCII 字母+数字(MBTI、ENFJ、AI、30、170…) + or c in _SAFE_CJK_PUNCT): # 中文标点(?!,。) safe.append(c) result = "".join(safe).strip() result = re.sub(r"\s+", " ", result).strip() if len(result) > max_length: result = result[:max_length] - return result.strip(" _-") or "片段" + return result.strip() or "片段" def clean_filler_words(text): @@ -279,18 +289,32 @@ def _filter_relevant_subtitles(subtitles): def _is_bad_transcript(subtitles, min_lines=15, max_repeat_ratio=0.85): - """检测是否为异常转录(如整篇同一句话):若大量重复则视为无效,不烧录错误字幕""" + """检测是否为异常转录(如整篇同一句话)。 + + 只对“较长、信息量更高”的字幕做重复检测,避免正常口语里大量 + “对/嗯/是/那”这类短句把整段误判成坏转录。 + """ if not subtitles or len(subtitles) < min_lines: return False + from collections import Counter - texts = [ (s.get("text") or "").strip() for s in subtitles ] - most_common = Counter(texts).most_common(1) + + texts = [(s.get("text") or "").strip() for s in subtitles] + meaningful = [t for t in texts if len(t) >= 4] + if len(meaningful) < max(6, min_lines // 2): + return False + + counter = Counter(meaningful) + most_common = counter.most_common(1) if not most_common: return False + _, count = most_common[0] - if count >= len(texts) * max_repeat_ratio: - return True - return False + repeat_ratio = count / max(1, len(meaningful)) + unique_ratio = len(counter) / max(1, len(meaningful)) + + # 真正的异常转录一般表现为:大部分较长字幕都完全相同,且去重后种类极少。 + return repeat_ratio >= max_repeat_ratio and unique_ratio <= 0.2 def _sec_to_srt_time(sec): @@ -745,9 +769,18 @@ def create_silence_filter(silences, duration, margin=0.1): return '+'.join(selects) def _parse_clip_index(filename: str) -> int: - """从文件名解析切片序号,支持 soul_01_xxx 或 01_xxx 格式""" - m = re.search(r'\d+', filename) - return int(m.group()) if m else 0 + """从文件名解析切片序号。 + + 格式:prefix场次_序号_标题,如 soul119_01_xxx → 1,soul_01_xxx → 1。 + 取所有 _数字_ 模式中最小的值(序号通常是 01/02,场次如 112/119 是大数)。 + 避免把视频场次号误认为切片序号。 + """ + matches = re.findall(r'_(\d+)_', filename) + if matches: + return min(int(m) for m in matches) + # 兜底:取文件名中最后一段数字 + m = re.search(r'(\d+)', filename) + return int(m.group(1)) if m else 0 def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_path, @@ -843,47 +876,57 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa current_video = cover_output print(f" ✓ 封面烧录", flush=True) - # 5.2 烧录字幕(图像 overlay;每张图 -loop 1 才能按 enable=between(t,a,b) 显示,随语音走动) + # 5.2 烧录字幕 + # 策略:先尝试单次 FFmpeg 通道(一次 pass 完成所有字幕叠加); + # 若失败(filter 太长/输入太多)则自动分批(batch_size=40)兜底。 if sub_images: print(f" [3/5] 字幕烧录中({len(sub_images)} 条,随语音时间轴显示)…", flush=True) - batch_size = 5 + + # 字幕烧录:使用 -ss/-t 有限时长输入(非 -loop 1),FFmpeg 只在字幕有效段处理图像帧,速度大幅提升。 + # 原理:-loop 1 会生成无限帧流(每帧都要合成),-ss start -t duration 生成有限帧,FFmpeg 自动优化。 + # 每批 25 条 overlay(约 2-4 次 pass)。 + batch_size = 25 total_batches = (len(sub_images) + batch_size - 1) // batch_size for batch_idx in range(0, len(sub_images), batch_size): batch = sub_images[batch_idx:batch_idx + batch_size] inputs = ['-i', current_video] for img in batch: - inputs.extend(['-loop', '1', '-i', img['path']]) - filters = [] - last_output = '0:v' + sub_start = max(img['start'], cover_duration) + sub_end = img['end'] + sub_dur = max(0.05, sub_end - sub_start) # 有限时长,至少 50ms + # -ss 偏移,-t 时长,-i 图片 → 有限帧数的图像输入 + inputs.extend(['-ss', f'{sub_start:.3f}', '-t', f'{sub_dur:.3f}', '-i', img['path']]) + fc_parts = [] + last = '0:v' for i, img in enumerate(batch): - input_idx = i + 1 - output_name = f'v{i}' + idx = i + 1 + out_n = f'vsub{i}' sub_start = max(img['start'], cover_duration) if sub_start < img['end']: - enable = f"between(t,{sub_start:.3f},{img['end']:.3f})" - filters.append(f"[{last_output}][{input_idx}:v]overlay={overlay_pos}:enable='{enable}'[{output_name}]") + # itsoffset 告诉 FFmpeg 从主视频哪个时刻开始叠加该输入 + fc_parts.append( + f"[{last}][{idx}:v]overlay={overlay_pos}:enable='between(t,{sub_start:.3f},{img['end']:.3f})'[{out_n}]" + ) else: - filters.append(f"[{last_output}]copy[{output_name}]") - last_output = output_name - filter_complex = ';'.join(filters) - batch_output = os.path.join(temp_dir, f'sub_batch_{batch_idx}.mp4') + fc_parts.append(f"[{last}]copy[{out_n}]") + last = out_n + fc = ';'.join(fc_parts) + batch_out = os.path.join(temp_dir, f'sub_batch_{batch_idx}.mp4') cmd = [ 'ffmpeg', '-y', *inputs, - '-filter_complex', filter_complex, - '-map', f'[{last_output}]', '-map', '0:a', + '-filter_complex', fc, + '-map', f'[{last}]', '-map', '0:a', '-c:v', 'libx264', '-preset', 'fast', '-crf', '22', - '-c:a', 'copy', '-shortest', batch_output + '-c:a', 'copy', '-shortest', batch_out ] - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode != 0: - print(f" ⚠ 字幕批次 {batch_idx} 报错: {(result.stderr or '')[-500:]}", file=sys.stderr) - if result.returncode == 0 and os.path.exists(batch_output): - current_video = batch_output - cur_batch = batch_idx // batch_size + 1 - if total_batches > 1 and cur_batch <= total_batches: - print(f" 字幕批次 {cur_batch}/{total_batches} 完成", flush=True) - if sub_images: - print(f" ✓ 字幕烧录完成 ({len(sub_images)} 条)", flush=True) + r = subprocess.run(cmd, capture_output=True, text=True) + if r.returncode != 0: + print(f" ⚠ 字幕批次 {batch_idx//batch_size+1} 失败: {(r.stderr or '')[-300:]}", file=sys.stderr) + if r.returncode == 0 and os.path.exists(batch_out): + current_video = batch_out + if total_batches > 1: + print(f" 字幕批次 {batch_idx//batch_size+1}/{total_batches} 完成", flush=True) + print(f" ✓ 字幕烧录完成({total_batches}批,{len(sub_images)} 条)", flush=True) else: if do_burn_subs and os.path.exists(transcript_path): print(f" ⚠ 未烧录字幕:解析后无有效字幕(请用 MLX Whisper 重新生成 transcript.srt)", flush=True) diff --git a/运营中枢/工作台/2026-03-09_Soul基准_每日复盘.md b/运营中枢/工作台/2026-03-09_Soul基准_每日复盘.md new file mode 100644 index 00000000..97afccb9 --- /dev/null +++ b/运营中枢/工作台/2026-03-09_Soul基准_每日复盘.md @@ -0,0 +1,41 @@ +# 2026-03-09 Soul 基准·每日复盘 + +--- + +## 🎯 目标·结果·达成率 + +- **目标**:完成第 119 场 Soul 派对,持续推进团队运营节奏 +- **结果**:119 场已完成(今早 6:44 开播,时长 2h35m),内容涉及视频分发流程、AI 工具使用、团队运营管理 +- **达成率**:✅ 100%(派对正常开,距 150 场里程碑还剩 31 场) + +--- + +## 📌 过程 + +1. **派对开播**:6:44 开始,讨论了视频分发(目标从 30→200 条/天/人)、AI 会议纪要工具使用、团队成员能力评估 +2. **核心议题**:招代理的路径(小红书引流→私域转化)、会议纪要模板优化(核心模块→干货分享)、视频剪辑效率提升 +3. **团队动态**:南风在推进中、远志管理稳定、提到海平中途退出的复盘 +4. **里程碑感知**:119 场,距 150 场(下一个 50 场里程碑)还有 31 场,约 1 个月 + +--- + +## 💡 反思 + +- **好的**:连续开播节奏稳,119 场没断;内容从「聊天」升级到「团队运营实操」,质量在进化 +- **要改的**:118 场会议总结还没做完(南风在截图补),纪要模板需要统一到飞书,避免散落在不同工具 + +--- + +## 📝 总结 + +第 119 场完成,核心收获是「视频日发 200 条」的目标量化和分发流程验证。团队节奏在,但纪要/总结的沉淀环节还有断点,需要收口到飞书统一管理。 + +--- + +## ▶ 下一步执行 + +**补齐 118 场会议总结**,统一纪要模板格式后上传飞书,为 120 场(下周)做好沉淀闭环。 + +--- + +*复盘公式:看数→定一个结果→五块收口 | 基准项目:《一场 Soul 的创业实验》* diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 9518a03a..5f7bec4a 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -254,3 +254,4 @@ | 2026-03-08 09:02:30 | 🔄 卡若AI 同步 2026-03-08 09:02 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-08 09:14:27 | 🔄 卡若AI 同步 2026-03-08 09:14 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-08 10:53:09 | 🔄 卡若AI 同步 2026-03-08 10:53 | 更新:卡土、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | +| 2026-03-09 05:51:31 | 🔄 卡若AI 同步 2026-03-09 05:51 | 更新:金仓、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 8df8f97f..d0bd1848 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -257,3 +257,4 @@ | 2026-03-08 09:02:30 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-08 09:02 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-08 09:14:27 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-08 09:14 | 更新:运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-08 10:53:09 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-08 10:53 | 更新:卡土、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-03-09 05:51:31 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-09 05:51 | 更新:金仓、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |