From aa5112e985bc788e698c4826661f1e50e7ea7d72 Mon Sep 17 00:00:00 2001 From: nirolee Date: Sun, 14 Jun 2026 15:18:05 +0900 Subject: [PATCH] fix(youtube): wait for upload to finish before publishing; add optional proxy - Wait until the upload progress reaches 100% before clicking publish. The browser-based upload only progresses while the page is open, so publishing (and closing the browser) mid-upload cut it off and left the video stuck partway (e.g. 76%). - Add optional YT_PROXY (conf.py): where youtube.com is blocked, direct connections time out and the chromium does not use the system proxy, so allow pointing it at a local proxy explicitly. --- README.md | 2 ++ conf.example.py | 4 +++ uploader/youtube_uploader/main.py | 45 ++++++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e5188e1..44ebe4f 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,8 @@ sau youtube upload-video --account --file videos/demo.mp4 --title > 是因为**未通过 Google 合规审核的 API 项目上传的视频会被强制锁为私享、无法改公开**,对个人/单频道不实用; > 浏览器自动化没有此限制,可直接发布公开视频,也与本项目其它平台的 cookie 方案一致。 > `--playlist` 适合连载/系列追更;`--visibility` 可选 `public`/`unlisted`/`private`。 +> 上传会**等进度到 100% 再点发布**(浏览器上传靠窗口开着传,传一半就发布会被掐断卡在中途)。 +> youtube.com 被墙的地区:在 `conf.py` 设 `YT_PROXY = "http://127.0.0.1:7890"`(chromium 不吃系统代理,需显式指定)。 补充说明: diff --git a/conf.example.py b/conf.example.py index 11e33c6..cf2e942 100644 --- a/conf.example.py +++ b/conf.example.py @@ -5,3 +5,7 @@ XHS_SERVER = "http://127.0.0.1:11901" # only used by xhs-related flows LOCAL_CHROME_PATH = "" # optional, e.g. C:/Program Files/Google/Chrome/Application/chrome.exe LOCAL_CHROME_HEADLESS = True # default headless behavior for uploader/examples DEBUG_MODE = True # default debug behavior +# Optional proxy for the YouTube uploader. Where youtube.com is blocked, direct +# connections time out and the (patchright) chromium does NOT use the system proxy. +# Point this at your local proxy port, e.g. "http://127.0.0.1:7890". None = no proxy. +YT_PROXY = None diff --git a/uploader/youtube_uploader/main.py b/uploader/youtube_uploader/main.py index 8fd4456..a036e24 100644 --- a/uploader/youtube_uploader/main.py +++ b/uploader/youtube_uploader/main.py @@ -21,6 +21,13 @@ from uploader.base_video import BaseVideoUploader from utils.base_social_media import set_init_script from utils.log import youtube_logger +try: + # 国内直连 youtube.com 会超时,且 patchright 启的 chromium 不吃系统代理。 + # 在 conf.py 设 YT_PROXY = "http://127.0.0.1:7890"(本地代理端口)即可;不设则不走代理。 + from conf import YT_PROXY +except Exception: + YT_PROXY = None + STUDIO_URL = "https://studio.youtube.com" UPLOAD_URL = "https://www.youtube.com/upload" VISIBILITY = {"public": "PUBLIC", "unlisted": "UNLISTED", "private": "PRIVATE"} @@ -121,6 +128,34 @@ async def _click_if_present(page: Page, selector: str, timeout: int = 4000) -> b return False +async def _wait_upload_complete(page: Page, max_polls: int = 360) -> bool: + """等网页上传从 X% 跑到 100% 再发布。浏览器上传靠窗口开着才传得完, + 若上传到一半就点发布并关闭浏览器,上传会被掐断卡在中途(如 76%)。 + 出现“处理/检查/上传完成”或不再“正在上传”即视为传完。max_polls*5s=30min 上限。""" + last = "" + for _ in range(max_polls): + txt = "" + for sel in (".progress-label", "span.progress-label", "ytcp-video-upload-progress"): + loc = page.locator(sel).first + try: + if await loc.count(): + txt = (await loc.inner_text()).strip() + if txt: + break + except Exception: + pass + if txt: + if any(k in txt for k in ("处理", "检查", "上传完成", "已上传", "Processing", "complete", "Checks", "Finished")): + youtube_logger.info(_msg("✅", f"上传完成: {txt[:40]}")) + return True + if txt != last: + youtube_logger.info(_msg("⏳", f"上传中: {txt[:40]}")) + last = txt + await page.wait_for_timeout(5000) + youtube_logger.warning(_msg("⚠️", "等上传超时(30min),仍尝试发布")) + return False + + class YouTubeVideo(BaseVideoUploader): def __init__(self, title, file_path, tags, account_file, *, description="", thumbnail_path=None, playlist=None, @@ -137,7 +172,10 @@ class YouTubeVideo(BaseVideoUploader): self.headless = headless async def upload(self, playwright: Playwright) -> None: - browser = await playwright.chromium.launch(headless=self.headless, channel="chrome") + browser = await playwright.chromium.launch( + headless=self.headless, channel="chrome", + proxy={"server": YT_PROXY} if YT_PROXY else None, + ) context = await browser.new_context(storage_state=self.account_file) context = await set_init_script(context) page = await context.new_page() @@ -235,6 +273,11 @@ class YouTubeVideo(BaseVideoUploader): youtube_logger.info(_msg("🌐", f"设置可见性 = {self.visibility}")) await _click_if_present(page, f"tp-yt-paper-radio-button[name='{VISIBILITY[self.visibility]}']", 10000) + # 10.5) 关键:等上传真正传完再发布。浏览器上传靠窗口开着传, + # 传到一半就点发布+关浏览器 = 上传被掐断卡在中途(如 76%)。 + youtube_logger.info(_msg("📤", "等待上传完成(传完才发布)…")) + await _wait_upload_complete(page) + # 11) 发布 await page.wait_for_timeout(1200) if not await _click_if_present(page, "#done-button", 15000):