feat(douyin): 添加 --bgm 参数支持选择音乐

This commit is contained in:
billy
2026-06-16 15:51:10 +08:00
parent 9c3d8de82d
commit d826a19bb2
3 changed files with 83 additions and 2 deletions
+4 -2
View File
@@ -105,9 +105,11 @@ sau douyin check --account <account_name>
sau douyin upload-video --account <account_name> --file videos/demo.mp4 --title "示例标题" --desc "示例简介"
图文正文1:
$noteText = @"图文正文"@
sau douyin upload-note --account <account_name> --images videos/demo1.png videos/demo2.png --title "图文标题" --note $noteText --tags 'tag1,tag2'
sau douyin upload-note --account <account_name> --images videos/demo1.png videos/demo2.png --title "图文标题" --note $noteText --tags 'tag1,tag2'
图文正文2:
sau douyin upload-note --account <account_name> --images videos/demo1.png videos/demo2.png --title "图文标题" --notef '图文文件路径' --tags 'tag1,tag2'
sau douyin upload-note --account <account_name> --images videos/demo1.png videos/demo2.png --title "图文标题" --notef '图文文件路径' --tags 'tag1,tag2'
添加 BGM(可选):
sau douyin upload-note --account <account_name> --images videos/demo1.png videos/demo2.png --title "图文标题" --note $noteText --tags 'tag1,tag2' --bgm '音乐名称'
```
抖音卡login手动获取cookie:
+4
View File
@@ -74,6 +74,7 @@ class DouyinNoteUploadRequest:
publish_strategy: str = DOUYIN_PUBLISH_STRATEGY_IMMEDIATE
debug: bool = True
headless: bool = True
bgm: str = ""
@dataclass(slots=True)
@@ -323,6 +324,7 @@ async def upload_note(request: DouyinNoteUploadRequest) -> Path:
publish_strategy=request.publish_strategy,
debug=request.debug,
headless=request.headless,
bgm=request.bgm,
)
await app.douyin_upload_note()
return account_file
@@ -549,6 +551,7 @@ def build_parser() -> argparse.ArgumentParser:
upload_note_parser.add_argument("--note", default="", help="Optional note content")
upload_note_parser.add_argument("--notef", default="", help="Read note content from file (txt/md)")
upload_note_parser.add_argument("--tags", default="", help="Comma-separated tags, such as tag1,tag2")
upload_note_parser.add_argument("--bgm", default="", help="BGM music name to search and select")
upload_note_parser.add_argument("--schedule", type=schedule_value, help=f"Schedule time in {schedule_help}")
add_runtime_flags(upload_note_parser)
@@ -707,6 +710,7 @@ async def dispatch(args: argparse.Namespace) -> int:
publish_strategy=publish_strategy,
debug=args.debug,
headless=args.headless,
bgm=args.bgm or "",
)
await upload_note(request)
print(f"Douyin note upload submitted: {len(request.image_files)} images")
+75
View File
@@ -400,6 +400,72 @@ class DouYinBaseUploader(BaseVideoUploader):
except Exception as exc:
douyin_logger.warning(_msg("🧾", f"自主声明设置失败,跳过该步骤继续发布:{exc}"))
async def select_bgm(self, page: Page, bgm_name: str) -> bool:
"""为图文发布选择 BGM:可选增强功能,搜索无结果或异常均跳过不中断发布。"""
try:
# 点击「选择音乐」按钮
music_entry = page.locator('text="选择音乐"').nth(1)
if not await music_entry.count():
music_entry = page.locator('text="选择音乐"').first
await music_entry.wait_for(state="visible", timeout=10000)
await music_entry.click()
# 等待侧边栏出现并搜索
sidesheet = page.locator(".semi-sidesheet-content").first
await sidesheet.wait_for(state="visible", timeout=8000)
search_input = sidesheet.locator('input.semi-input[placeholder="搜索音乐"]').first
await search_input.wait_for(state="visible", timeout=5000)
await search_input.fill(bgm_name)
await search_input.press("Enter")
# 等待搜索结果
await asyncio.sleep(2)
first_card = sidesheet.locator(".card-container-tmocjc").first
try:
await first_card.wait_for(state="visible", timeout=8000)
except Exception:
douyin_logger.warning(_msg("🎵", f"音乐「{bgm_name}」搜索结果为空,小人跳过"))
await self._close_music_sidesheet(page)
return False
# 打印找到的音乐名称
try:
song_name_el = first_card.locator(".song-name-oRge4d").first
if await song_name_el.count():
song_name = await song_name_el.inner_text()
douyin_logger.info(_msg("🎵", f"小人找到了: {song_name}"))
except Exception:
pass
# JS 点击「使用」(按钮 visibility:hidden,普通 click 无效)
apply_btn = first_card.locator(".apply-btn-LUPP0D").first
await apply_btn.evaluate("el => el.click()")
douyin_logger.info(_msg("🥳", f"BGM「{bgm_name}」已应用"))
# 等待侧边栏关闭,超时则手动关闭
try:
await sidesheet.wait_for(state="hidden", timeout=5000)
except Exception:
await self._close_music_sidesheet(page)
return True
except Exception as exc:
douyin_logger.warning(_msg("🎵", f"添加 BGM 时出错,跳过该步骤继续发布:{exc}"))
try:
await self._close_music_sidesheet(page)
except Exception:
pass
return False
async def _close_music_sidesheet(self, page: Page) -> None:
try:
close_btn = page.locator(".semi-sidesheet-close").first
if await close_btn.count() and await close_btn.is_visible():
await close_btn.click()
await asyncio.sleep(1)
except Exception:
pass
class DouYinVideo(DouYinBaseUploader):
def __init__(
@@ -619,6 +685,7 @@ class DouYinNote(DouYinBaseUploader):
publish_strategy: str = DOUYIN_PUBLISH_STRATEGY_IMMEDIATE,
debug: bool = DEBUG_MODE,
headless: bool = LOCAL_CHROME_HEADLESS,
bgm: str = "",
):
super().__init__(
publish_date=publish_date,
@@ -631,6 +698,7 @@ class DouYinNote(DouYinBaseUploader):
self.note = note or ""
self.title = title or (self.note[:30] if self.note else "")
self.tags = tags or []
self.bgm = bgm or ""
async def validate_upload_args(self):
await self.validate_base_args()
@@ -682,8 +750,15 @@ class DouYinNote(DouYinBaseUploader):
await asyncio.sleep(1)
douyin_logger.info(_msg("✍️", "小人开始填标题、描述和话题"))
await self.fill_title_and_description(page, self.title, self.note, self.tags)
title_len = len(self.title) if self.title else 0
tags_text = " ".join(f"#{t}" for t in self.tags) if self.tags else ""
desc_and_tags_len = len(self.note or "") + (len(tags_text) + 2 if self.tags else 0)
douyin_logger.info(_msg("📝", f"标题总字数: {title_len},描述+话题总字数: {desc_and_tags_len}"))
douyin_logger.info(_msg("🏷️", f"小人一共贴了 {len(self.tags)} 个话题"))
if self.bgm:
await self.select_bgm(page, self.bgm)
if self.publish_strategy == DOUYIN_PUBLISH_STRATEGY_SCHEDULED and self.publish_date != 0:
await self.set_schedule_time_douyin(page, self.publish_date)