Merge pull request #216 from yoqu/codex/dual-cover-aspects
feat(cli): 支持双比例封面参数
This commit is contained in:
@@ -141,8 +141,16 @@ CLI 将 `debug` 和 `headless` 拆成了两个独立维度:
|
|||||||
--desc "示例简介"
|
--desc "示例简介"
|
||||||
--tags 运动,训练
|
--tags 运动,训练
|
||||||
--thumbnail videos/demo.png
|
--thumbnail videos/demo.png
|
||||||
|
--thumbnail-landscape videos/cover-4x3.png
|
||||||
|
--thumbnail-portrait videos/cover-3x4.png
|
||||||
```
|
```
|
||||||
|
|
||||||
|
抖音和视频号支持同时设置两种比例的封面图:
|
||||||
|
|
||||||
|
- `--thumbnail-landscape`: 4:3 横版封面
|
||||||
|
- `--thumbnail-portrait`: 3:4 竖版封面
|
||||||
|
- `--thumbnail`: 兼容旧参数,等同于 3:4 竖版封面
|
||||||
|
|
||||||
抖音额外支持:
|
抖音额外支持:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+26
-3
@@ -54,6 +54,8 @@ class DouyinVideoUploadRequest:
|
|||||||
tags: list[str]
|
tags: list[str]
|
||||||
publish_date: datetime | int
|
publish_date: datetime | int
|
||||||
thumbnail_file: Path | None = None
|
thumbnail_file: Path | None = None
|
||||||
|
thumbnail_landscape_file: Path | None = None
|
||||||
|
thumbnail_portrait_file: Path | None = None
|
||||||
product_link: str = ""
|
product_link: str = ""
|
||||||
product_title: str = ""
|
product_title: str = ""
|
||||||
publish_strategy: str = DOUYIN_PUBLISH_STRATEGY_IMMEDIATE
|
publish_strategy: str = DOUYIN_PUBLISH_STRATEGY_IMMEDIATE
|
||||||
@@ -148,6 +150,8 @@ class TencentVideoUploadRequest:
|
|||||||
tags: list[str]
|
tags: list[str]
|
||||||
publish_date: datetime | int
|
publish_date: datetime | int
|
||||||
thumbnail_file: Path | None = None
|
thumbnail_file: Path | None = None
|
||||||
|
thumbnail_landscape_file: Path | None = None
|
||||||
|
thumbnail_portrait_file: Path | None = None
|
||||||
short_title: str | None = None
|
short_title: str | None = None
|
||||||
category: str | None = None
|
category: str | None = None
|
||||||
is_draft: bool = False
|
is_draft: bool = False
|
||||||
@@ -285,7 +289,12 @@ async def upload_video(request: DouyinVideoUploadRequest) -> Path:
|
|||||||
request.publish_date,
|
request.publish_date,
|
||||||
str(account_file),
|
str(account_file),
|
||||||
desc=request.description,
|
desc=request.description,
|
||||||
thumbnail_portrait_path=str(request.thumbnail_file) if request.thumbnail_file else None,
|
thumbnail_landscape_path=(
|
||||||
|
str(request.thumbnail_landscape_file) if request.thumbnail_landscape_file else None
|
||||||
|
),
|
||||||
|
thumbnail_portrait_path=str(
|
||||||
|
request.thumbnail_portrait_file or request.thumbnail_file
|
||||||
|
) if request.thumbnail_portrait_file or request.thumbnail_file else None,
|
||||||
productLink=request.product_link,
|
productLink=request.product_link,
|
||||||
productTitle=request.product_title,
|
productTitle=request.product_title,
|
||||||
publish_strategy=request.publish_strategy,
|
publish_strategy=request.publish_strategy,
|
||||||
@@ -463,6 +472,12 @@ async def upload_tencent_video(request: TencentVideoUploadRequest) -> Path:
|
|||||||
is_draft=request.is_draft,
|
is_draft=request.is_draft,
|
||||||
desc=request.description,
|
desc=request.description,
|
||||||
thumbnail_path=str(request.thumbnail_file) if request.thumbnail_file else None,
|
thumbnail_path=str(request.thumbnail_file) if request.thumbnail_file else None,
|
||||||
|
thumbnail_landscape_path=(
|
||||||
|
str(request.thumbnail_landscape_file) if request.thumbnail_landscape_file else None
|
||||||
|
),
|
||||||
|
thumbnail_portrait_path=(
|
||||||
|
str(request.thumbnail_portrait_file) if request.thumbnail_portrait_file else None
|
||||||
|
),
|
||||||
short_title=request.short_title,
|
short_title=request.short_title,
|
||||||
publish_strategy=request.publish_strategy,
|
publish_strategy=request.publish_strategy,
|
||||||
debug=request.debug,
|
debug=request.debug,
|
||||||
@@ -520,7 +535,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
upload_video_parser.add_argument("--desc", default="", help="Optional video description")
|
upload_video_parser.add_argument("--desc", default="", help="Optional video description")
|
||||||
upload_video_parser.add_argument("--tags", default="", help="Comma-separated tags, such as tag1,tag2")
|
upload_video_parser.add_argument("--tags", default="", help="Comma-separated tags, such as tag1,tag2")
|
||||||
upload_video_parser.add_argument("--schedule", type=schedule_value, help=f"Schedule time in {schedule_help}")
|
upload_video_parser.add_argument("--schedule", type=schedule_value, help=f"Schedule time in {schedule_help}")
|
||||||
upload_video_parser.add_argument("--thumbnail", type=existing_file_path, help="Optional thumbnail path")
|
upload_video_parser.add_argument("--thumbnail", type=existing_file_path, help="Optional 3:4 portrait thumbnail path")
|
||||||
|
upload_video_parser.add_argument("--thumbnail-landscape", type=existing_file_path, help="Optional 4:3 landscape thumbnail path")
|
||||||
|
upload_video_parser.add_argument("--thumbnail-portrait", type=existing_file_path, help="Optional 3:4 portrait thumbnail path")
|
||||||
upload_video_parser.add_argument("--product-link", default="", help="Optional product link")
|
upload_video_parser.add_argument("--product-link", default="", help="Optional product link")
|
||||||
upload_video_parser.add_argument("--product-title", default="", help="Optional product title")
|
upload_video_parser.add_argument("--product-title", default="", help="Optional product title")
|
||||||
add_runtime_flags(upload_video_parser)
|
add_runtime_flags(upload_video_parser)
|
||||||
@@ -622,7 +639,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
tencent_upload_video_parser.add_argument("--desc", default="", help="Optional video description")
|
tencent_upload_video_parser.add_argument("--desc", default="", help="Optional video description")
|
||||||
tencent_upload_video_parser.add_argument("--tags", default="", help="Comma-separated tags, such as tag1,tag2")
|
tencent_upload_video_parser.add_argument("--tags", default="", help="Comma-separated tags, such as tag1,tag2")
|
||||||
tencent_upload_video_parser.add_argument("--schedule", type=schedule_value, help=f"Schedule time in {schedule_help}")
|
tencent_upload_video_parser.add_argument("--schedule", type=schedule_value, help=f"Schedule time in {schedule_help}")
|
||||||
tencent_upload_video_parser.add_argument("--thumbnail", type=existing_file_path, help="Optional thumbnail path")
|
tencent_upload_video_parser.add_argument("--thumbnail", type=existing_file_path, help="Optional 3:4 portrait thumbnail path")
|
||||||
|
tencent_upload_video_parser.add_argument("--thumbnail-landscape", type=existing_file_path, help="Optional 4:3 landscape thumbnail path")
|
||||||
|
tencent_upload_video_parser.add_argument("--thumbnail-portrait", type=existing_file_path, help="Optional 3:4 portrait thumbnail path")
|
||||||
tencent_upload_video_parser.add_argument("--short-title", help="Optional WeChat Channels short title")
|
tencent_upload_video_parser.add_argument("--short-title", help="Optional WeChat Channels short title")
|
||||||
tencent_upload_video_parser.add_argument("--category", help="Optional original content category")
|
tencent_upload_video_parser.add_argument("--category", help="Optional original content category")
|
||||||
tencent_upload_video_parser.add_argument("--draft", action="store_true", help="Save as draft instead of publishing")
|
tencent_upload_video_parser.add_argument("--draft", action="store_true", help="Save as draft instead of publishing")
|
||||||
@@ -655,6 +674,8 @@ async def dispatch(args: argparse.Namespace) -> int:
|
|||||||
tags=parse_tags(args.tags),
|
tags=parse_tags(args.tags),
|
||||||
publish_date=args.schedule or 0,
|
publish_date=args.schedule or 0,
|
||||||
thumbnail_file=args.thumbnail,
|
thumbnail_file=args.thumbnail,
|
||||||
|
thumbnail_landscape_file=args.thumbnail_landscape,
|
||||||
|
thumbnail_portrait_file=args.thumbnail_portrait,
|
||||||
product_link=args.product_link,
|
product_link=args.product_link,
|
||||||
product_title=args.product_title,
|
product_title=args.product_title,
|
||||||
publish_strategy=publish_strategy,
|
publish_strategy=publish_strategy,
|
||||||
@@ -838,6 +859,8 @@ async def dispatch(args: argparse.Namespace) -> int:
|
|||||||
tags=parse_tags(args.tags),
|
tags=parse_tags(args.tags),
|
||||||
publish_date=args.schedule or 0,
|
publish_date=args.schedule or 0,
|
||||||
thumbnail_file=args.thumbnail,
|
thumbnail_file=args.thumbnail,
|
||||||
|
thumbnail_landscape_file=args.thumbnail_landscape,
|
||||||
|
thumbnail_portrait_file=args.thumbnail_portrait,
|
||||||
short_title=args.short_title,
|
short_title=args.short_title,
|
||||||
category=args.category,
|
category=args.category,
|
||||||
is_draft=args.draft,
|
is_draft=args.draft,
|
||||||
|
|||||||
@@ -38,6 +38,66 @@ class BrowserCliParserTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(args.desc, "视频简介")
|
self.assertEqual(args.desc, "视频简介")
|
||||||
|
|
||||||
|
def test_douyin_upload_video_accepts_dual_thumbnail_aspects(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
video_path = Path(tmp_dir) / "demo.mp4"
|
||||||
|
landscape_path = Path(tmp_dir) / "landscape.png"
|
||||||
|
portrait_path = Path(tmp_dir) / "portrait.png"
|
||||||
|
video_path.write_bytes(b"video")
|
||||||
|
landscape_path.write_bytes(b"image")
|
||||||
|
portrait_path.write_bytes(b"image")
|
||||||
|
|
||||||
|
parser = sau_cli.build_parser()
|
||||||
|
args = parser.parse_args(
|
||||||
|
[
|
||||||
|
"douyin",
|
||||||
|
"upload-video",
|
||||||
|
"--account",
|
||||||
|
"creator",
|
||||||
|
"--file",
|
||||||
|
str(video_path),
|
||||||
|
"--title",
|
||||||
|
"标题",
|
||||||
|
"--thumbnail-landscape",
|
||||||
|
str(landscape_path),
|
||||||
|
"--thumbnail-portrait",
|
||||||
|
str(portrait_path),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(args.thumbnail_landscape, landscape_path)
|
||||||
|
self.assertEqual(args.thumbnail_portrait, portrait_path)
|
||||||
|
|
||||||
|
def test_tencent_upload_video_accepts_dual_thumbnail_aspects(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
video_path = Path(tmp_dir) / "demo.mp4"
|
||||||
|
landscape_path = Path(tmp_dir) / "landscape.png"
|
||||||
|
portrait_path = Path(tmp_dir) / "portrait.png"
|
||||||
|
video_path.write_bytes(b"video")
|
||||||
|
landscape_path.write_bytes(b"image")
|
||||||
|
portrait_path.write_bytes(b"image")
|
||||||
|
|
||||||
|
parser = sau_cli.build_parser()
|
||||||
|
args = parser.parse_args(
|
||||||
|
[
|
||||||
|
"tencent",
|
||||||
|
"upload-video",
|
||||||
|
"--account",
|
||||||
|
"creator",
|
||||||
|
"--file",
|
||||||
|
str(video_path),
|
||||||
|
"--title",
|
||||||
|
"标题",
|
||||||
|
"--thumbnail-landscape",
|
||||||
|
str(landscape_path),
|
||||||
|
"--thumbnail-portrait",
|
||||||
|
str(portrait_path),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(args.thumbnail_landscape, landscape_path)
|
||||||
|
self.assertEqual(args.thumbnail_portrait, portrait_path)
|
||||||
|
|
||||||
def test_kuaishou_upload_note_accepts_title_and_note(self):
|
def test_kuaishou_upload_note_accepts_title_and_note(self):
|
||||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
image_path = Path(tmp_dir) / "1.png"
|
image_path = Path(tmp_dir) / "1.png"
|
||||||
@@ -135,6 +195,57 @@ class BrowserCliDispatchTests(unittest.TestCase):
|
|||||||
self.assertEqual(request.title, "图文标题")
|
self.assertEqual(request.title, "图文标题")
|
||||||
self.assertEqual(request.note, "图文正文")
|
self.assertEqual(request.note, "图文正文")
|
||||||
|
|
||||||
|
def test_dispatch_douyin_upload_video_uses_dual_thumbnail_request_fields(self):
|
||||||
|
args = Namespace(
|
||||||
|
platform="douyin",
|
||||||
|
action="upload-video",
|
||||||
|
account="creator",
|
||||||
|
file=Path("demo.mp4"),
|
||||||
|
title="视频标题",
|
||||||
|
desc="视频简介",
|
||||||
|
tags="测试,视频",
|
||||||
|
schedule=0,
|
||||||
|
thumbnail=None,
|
||||||
|
thumbnail_landscape=Path("landscape.png"),
|
||||||
|
thumbnail_portrait=Path("portrait.png"),
|
||||||
|
product_link="",
|
||||||
|
product_title="",
|
||||||
|
debug=False,
|
||||||
|
headless=True,
|
||||||
|
)
|
||||||
|
with patch("sau_cli.upload_video", new=AsyncMock()) as mock_upload:
|
||||||
|
asyncio.run(sau_cli.dispatch(args))
|
||||||
|
|
||||||
|
request = mock_upload.await_args.args[0]
|
||||||
|
self.assertEqual(request.thumbnail_landscape_file, Path("landscape.png"))
|
||||||
|
self.assertEqual(request.thumbnail_portrait_file, Path("portrait.png"))
|
||||||
|
|
||||||
|
def test_dispatch_tencent_upload_video_uses_dual_thumbnail_request_fields(self):
|
||||||
|
args = Namespace(
|
||||||
|
platform="tencent",
|
||||||
|
action="upload-video",
|
||||||
|
account="creator",
|
||||||
|
file=Path("demo.mp4"),
|
||||||
|
title="视频标题",
|
||||||
|
desc="视频简介",
|
||||||
|
tags="测试,视频",
|
||||||
|
schedule=0,
|
||||||
|
thumbnail=None,
|
||||||
|
thumbnail_landscape=Path("landscape.png"),
|
||||||
|
thumbnail_portrait=Path("portrait.png"),
|
||||||
|
short_title=None,
|
||||||
|
category=None,
|
||||||
|
draft=False,
|
||||||
|
debug=False,
|
||||||
|
headless=True,
|
||||||
|
)
|
||||||
|
with patch("sau_cli.upload_tencent_video", new=AsyncMock()) as mock_upload:
|
||||||
|
asyncio.run(sau_cli.dispatch(args))
|
||||||
|
|
||||||
|
request = mock_upload.await_args.args[0]
|
||||||
|
self.assertEqual(request.thumbnail_landscape_file, Path("landscape.png"))
|
||||||
|
self.assertEqual(request.thumbnail_portrait_file, Path("portrait.png"))
|
||||||
|
|
||||||
def test_dispatch_xiaohongshu_upload_video_uses_headed_request(self):
|
def test_dispatch_xiaohongshu_upload_video_uses_headed_request(self):
|
||||||
args = Namespace(
|
args = Namespace(
|
||||||
platform="xiaohongshu",
|
platform="xiaohongshu",
|
||||||
|
|||||||
@@ -698,6 +698,8 @@ class TencentVideo(TencentBaseUploader):
|
|||||||
is_draft=False,
|
is_draft=False,
|
||||||
desc: str | None = None,
|
desc: str | None = None,
|
||||||
thumbnail_path: str | None = None,
|
thumbnail_path: str | None = None,
|
||||||
|
thumbnail_landscape_path: str | None = None,
|
||||||
|
thumbnail_portrait_path: str | None = None,
|
||||||
short_title: str | None = None,
|
short_title: str | None = None,
|
||||||
publish_strategy: str = TENCENT_PUBLISH_STRATEGY_IMMEDIATE,
|
publish_strategy: str = TENCENT_PUBLISH_STRATEGY_IMMEDIATE,
|
||||||
debug: bool = DEBUG_MODE,
|
debug: bool = DEBUG_MODE,
|
||||||
@@ -717,6 +719,8 @@ class TencentVideo(TencentBaseUploader):
|
|||||||
self.is_draft = is_draft
|
self.is_draft = is_draft
|
||||||
self.desc = desc or ""
|
self.desc = desc or ""
|
||||||
self.thumbnail_path = thumbnail_path
|
self.thumbnail_path = thumbnail_path
|
||||||
|
self.thumbnail_landscape_path = thumbnail_landscape_path
|
||||||
|
self.thumbnail_portrait_path = thumbnail_portrait_path or thumbnail_path
|
||||||
self.short_title = short_title
|
self.short_title = short_title
|
||||||
|
|
||||||
async def validate_upload_args(self):
|
async def validate_upload_args(self):
|
||||||
@@ -724,8 +728,10 @@ class TencentVideo(TencentBaseUploader):
|
|||||||
if not self.title or not str(self.title).strip():
|
if not self.title or not str(self.title).strip():
|
||||||
raise ValueError("视频模式下,title 是必须的")
|
raise ValueError("视频模式下,title 是必须的")
|
||||||
self.file_path = str(self.validate_video_file(self.file_path))
|
self.file_path = str(self.validate_video_file(self.file_path))
|
||||||
if self.thumbnail_path:
|
if self.thumbnail_landscape_path:
|
||||||
self.thumbnail_path = str(self.validate_image_file(self.thumbnail_path))
|
self.thumbnail_landscape_path = str(self.validate_image_file(self.thumbnail_landscape_path))
|
||||||
|
if self.thumbnail_portrait_path:
|
||||||
|
self.thumbnail_portrait_path = str(self.validate_image_file(self.thumbnail_portrait_path))
|
||||||
|
|
||||||
async def handle_upload_error(self, page: Page) -> None:
|
async def handle_upload_error(self, page: Page) -> None:
|
||||||
tencent_logger.info(_msg("😵", "视频出错了,重新上传中"))
|
tencent_logger.info(_msg("😵", "视频出错了,重新上传中"))
|
||||||
@@ -733,18 +739,8 @@ class TencentVideo(TencentBaseUploader):
|
|||||||
await page.get_by_role("button", name="删除", exact=True).click()
|
await page.get_by_role("button", name="删除", exact=True).click()
|
||||||
await self.upload_video_file(page, self.file_path)
|
await self.upload_video_file(page, self.file_path)
|
||||||
|
|
||||||
async def set_thumbnail(self, page: Page) -> None:
|
async def open_thumbnail_dialog(self, page: Page, selectors: list[str], dialog_titles: list[str]):
|
||||||
if not self.thumbnail_path:
|
for selector in selectors:
|
||||||
return
|
|
||||||
|
|
||||||
tencent_logger.info(_msg("🖼️", "小人准备设置封面"))
|
|
||||||
|
|
||||||
cover_entry_selectors = [
|
|
||||||
'div.vertical-cover-wrap:has-text("个人主页卡片"):has-text("3:4")',
|
|
||||||
'div.vertical-cover-wrap:has-text("3:4")',
|
|
||||||
'div.vertical-cover-wrap:has-text("个人主页卡片")',
|
|
||||||
]
|
|
||||||
for selector in cover_entry_selectors:
|
|
||||||
cover_entry = page.locator(selector).first
|
cover_entry = page.locator(selector).first
|
||||||
try:
|
try:
|
||||||
if not await cover_entry.count():
|
if not await cover_entry.count():
|
||||||
@@ -756,42 +752,96 @@ class TencentVideo(TencentBaseUploader):
|
|||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cover_dialog = page.locator("div.weui-desktop-dialog").filter(has_text="编辑个人主页卡片").first
|
for title in dialog_titles:
|
||||||
if not await cover_dialog.count():
|
cover_dialog = page.locator("div.weui-desktop-dialog").filter(has_text=title).first
|
||||||
tencent_logger.info(_msg("🧍", "当前页面没有出现封面编辑弹窗,小人先跳过自定义封面"))
|
if await cover_dialog.count():
|
||||||
|
return cover_dialog
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def confirm_thumbnail_crop(self, page: Page) -> None:
|
||||||
|
crop_dialog = page.locator("div.weui-desktop-dialog").filter(has_text="裁剪封面图").first
|
||||||
|
if not await crop_dialog.count():
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await cover_dialog.wait_for(state="visible", timeout=5000)
|
await crop_dialog.wait_for(state="visible", timeout=10000)
|
||||||
except Exception:
|
crop_confirm_button = crop_dialog.locator(
|
||||||
tencent_logger.warning(_msg("😵", "封面编辑弹窗暂时不可见,这次先跳过自定义封面"))
|
'div.weui-desktop-dialog__ft button.weui-desktop-btn_primary:has-text("确定")'
|
||||||
return
|
).first
|
||||||
|
if await crop_confirm_button.count():
|
||||||
|
await crop_confirm_button.wait_for(state="visible", timeout=5000)
|
||||||
|
await crop_confirm_button.click()
|
||||||
|
await page.wait_for_timeout(1000)
|
||||||
|
except Exception as exc:
|
||||||
|
tencent_logger.warning(_msg("😵", f"封面裁剪确认时出错,小人继续尝试保存主弹窗: {exc}"))
|
||||||
|
|
||||||
|
async def upload_thumbnail_in_dialog(self, page: Page, cover_dialog, thumbnail_path: str) -> None:
|
||||||
|
await cover_dialog.wait_for(state="visible", timeout=5000)
|
||||||
file_input = cover_dialog.locator('.single-cover-uploader-wrap input[type="file"]').first
|
file_input = cover_dialog.locator('.single-cover-uploader-wrap input[type="file"]').first
|
||||||
await file_input.wait_for(state="attached", timeout=10000)
|
await file_input.wait_for(state="attached", timeout=10000)
|
||||||
await file_input.set_input_files(self.thumbnail_path)
|
await file_input.set_input_files(thumbnail_path)
|
||||||
await page.wait_for_timeout(1000)
|
await page.wait_for_timeout(1000)
|
||||||
|
await self.confirm_thumbnail_crop(page)
|
||||||
crop_dialog = page.locator("div.weui-desktop-dialog").filter(has_text="裁剪封面图").first
|
|
||||||
if await crop_dialog.count():
|
|
||||||
try:
|
|
||||||
await crop_dialog.wait_for(state="visible", timeout=10000)
|
|
||||||
crop_confirm_button = crop_dialog.locator(
|
|
||||||
'div.weui-desktop-dialog__ft button.weui-desktop-btn_primary:has-text("确定")'
|
|
||||||
).first
|
|
||||||
if await crop_confirm_button.count():
|
|
||||||
await crop_confirm_button.wait_for(state="visible", timeout=5000)
|
|
||||||
await crop_confirm_button.click()
|
|
||||||
await page.wait_for_timeout(1000)
|
|
||||||
except Exception as exc:
|
|
||||||
tencent_logger.warning(_msg("😵", f"封面裁剪确认时出错,小人继续尝试保存主弹窗: {exc}"))
|
|
||||||
|
|
||||||
confirm_button = cover_dialog.locator(
|
confirm_button = cover_dialog.locator(
|
||||||
'div.weui-desktop-dialog__ft button.weui-desktop-btn_primary:has-text("确认")'
|
'div.weui-desktop-dialog__ft button.weui-desktop-btn_primary:has-text("确认")'
|
||||||
).first
|
).first
|
||||||
await confirm_button.wait_for(state="visible", timeout=10000)
|
await confirm_button.wait_for(state="visible", timeout=10000)
|
||||||
await confirm_button.click()
|
await confirm_button.click()
|
||||||
tencent_logger.success(_msg("🥳", "封面已经设置完成"))
|
|
||||||
|
async def set_single_thumbnail(
|
||||||
|
self,
|
||||||
|
page: Page,
|
||||||
|
thumbnail_path: str,
|
||||||
|
selectors: list[str],
|
||||||
|
dialog_titles: list[str],
|
||||||
|
label: str,
|
||||||
|
) -> None:
|
||||||
|
cover_dialog = await self.open_thumbnail_dialog(page, selectors, dialog_titles)
|
||||||
|
if not cover_dialog:
|
||||||
|
tencent_logger.info(_msg("🧍", f"当前页面没有出现{label}封面编辑弹窗,小人先跳过"))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.upload_thumbnail_in_dialog(page, cover_dialog, thumbnail_path)
|
||||||
|
tencent_logger.success(_msg("🥳", f"{label}封面已经设置完成"))
|
||||||
|
except Exception as exc:
|
||||||
|
tencent_logger.warning(_msg("😵", f"{label}封面设置失败,这次先跳过: {exc}"))
|
||||||
|
|
||||||
|
async def set_thumbnail(self, page: Page) -> None:
|
||||||
|
if not self.thumbnail_landscape_path and not self.thumbnail_portrait_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
tencent_logger.info(_msg("🖼️", "小人准备设置封面"))
|
||||||
|
|
||||||
|
landscape_selectors = [
|
||||||
|
'div.horizontal-cover-wrap:has-text("4:3")',
|
||||||
|
'div[class*="cover-wrap"]:has-text("4:3"):has-text("动态")',
|
||||||
|
'div:has-text("视频号动态"):has-text("4:3")',
|
||||||
|
'div:has-text("横版封面"):has-text("4:3")',
|
||||||
|
]
|
||||||
|
portrait_selectors = [
|
||||||
|
'div.vertical-cover-wrap:has-text("个人主页卡片"):has-text("3:4")',
|
||||||
|
'div.vertical-cover-wrap:has-text("3:4")',
|
||||||
|
'div.vertical-cover-wrap:has-text("个人主页卡片")',
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.thumbnail_landscape_path:
|
||||||
|
await self.set_single_thumbnail(
|
||||||
|
page,
|
||||||
|
self.thumbnail_landscape_path,
|
||||||
|
landscape_selectors,
|
||||||
|
["编辑视频号动态封面", "编辑动态封面", "编辑封面"],
|
||||||
|
"4:3 横版",
|
||||||
|
)
|
||||||
|
if self.thumbnail_portrait_path:
|
||||||
|
await self.set_single_thumbnail(
|
||||||
|
page,
|
||||||
|
self.thumbnail_portrait_path,
|
||||||
|
portrait_selectors,
|
||||||
|
["编辑个人主页卡片", "编辑封面"],
|
||||||
|
"3:4 竖版",
|
||||||
|
)
|
||||||
|
|
||||||
async def prepare_video_for_publish(self, page: Page) -> None:
|
async def prepare_video_for_publish(self, page: Page) -> None:
|
||||||
await self.fill_title_and_tags(page)
|
await self.fill_title_and_tags(page)
|
||||||
|
|||||||
Reference in New Issue
Block a user