From 6160ea5892c250b051397a8665dd0bc2bd6d964d Mon Sep 17 00:00:00 2001 From: Guillem Hernandez Sola Date: Tue, 19 May 2026 12:31:15 +0200 Subject: [PATCH] try --- jenkins/comedygoldbcnTw | 76 ++++++++++++++++++++++++++++++----------- tiktok2bsky.py | 48 +++++++++++++++++--------- 2 files changed, 89 insertions(+), 35 deletions(-) diff --git a/jenkins/comedygoldbcnTw b/jenkins/comedygoldbcnTw index 86585c9..f7e8f2e 100644 --- a/jenkins/comedygoldbcnTw +++ b/jenkins/comedygoldbcnTw @@ -23,31 +23,69 @@ pipeline { stage('Setup Python & Install Dependencies') { steps { sh ''' - set -e # Exit immediately if a command exits with a non-zero status - - # Create a virtual environment named 'venv' - python3 -m venv venv - - # Activate the virtual environment and install dependencies - . venv/bin/activate && \ - pip install --upgrade pip && \ - pip install -U atproto tweety-ns playwright httpx arrow python-dotenv moviepy grapheme - - # Check if moviepy is installed - pip list | grep moviepy || { echo 'MoviePy installation failed!'; exit 1; } - - # Check if FFmpeg is installed - ffmpeg -version || { echo 'FFmpeg is not installed!'; exit 1; } + set -e - # Verify that moviepy can be imported - python3 -c "import moviepy" || { echo 'MoviePy import failed!'; exit 1; } + # ── Create venv ──────────────────────────────────── + python3 -m venv venv - # Install the local browser binaries for this environment - playwright install chromium + # ── Upgrade pip toolchain ────────────────────────── + . venv/bin/activate + pip install --upgrade pip wheel setuptools + + # ── Core dependencies ────────────────────────────── + pip install -U \ + atproto \ + playwright \ + playwright-stealth \ + httpx \ + arrow \ + python-dotenv \ + moviepy \ + beautifulsoup4 \ + charset-normalizer \ + Pillow \ + grapheme + + # ── yt-dlp: always upgrade to latest ────────────── + pip install --upgrade yt-dlp + pip show yt-dlp | grep -E "^(Name|Version)" + + # ── curl_cffi: TikTok impersonation support ──────── + # Required by yt-dlp to bypass TikTok bot detection + pip install --upgrade curl-cffi + pip show curl-cffi | grep -E "^(Name|Version)" + + # ── playwright-stealth version check ─────────────── + pip show playwright-stealth | grep -E "^(Name|Version)" + python3 -c " + try: + from playwright_stealth import stealth_sync + print('playwright_stealth OK (v1.x - stealth_sync)') + except ImportError: + from playwright_stealth import Stealth + print('playwright_stealth OK (v2.x - Stealth class)') +" + + # ── Sanity checks ────────────────────────────────── + python3 -c "import atproto; print('atproto OK')" + python3 -c "import playwright; print('playwright OK')" + python3 -c "import yt_dlp; print('yt_dlp OK')" + python3 -c "import curl_cffi; print('curl_cffi OK')" + python3 -c "import httpx; print('httpx OK')" + python3 -c "import arrow; print('arrow OK')" + python3 -c "import moviepy; print('moviepy OK')" + + # ── System tools ─────────────────────────────────── + ffmpeg -version | head -1 + ffprobe -version | head -1 + + # ── Playwright browser binaries ──────────────────── + playwright install chromium ''' } } + stage('Run Script') { steps { // Securely injects Jenkins credentials as environment variables diff --git a/tiktok2bsky.py b/tiktok2bsky.py index 72bc2f3..f545eb4 100644 --- a/tiktok2bsky.py +++ b/tiktok2bsky.py @@ -542,18 +542,23 @@ def get_video_duration(path: str) -> float: def compress_video(input_path: str, output_path: str, max_duration: int = VIDEO_MAX_DURATION_S, max_size_bytes: int = VIDEO_MAX_SIZE_BYTES) -> bool: - """ - Trim to max_duration and compress to fit max_size_bytes. - Returns True on success. - """ try: duration = get_video_duration(input_path) - trim_to = min(duration, max_duration) - # Target bitrate calculation (leave 10 % headroom) + # Guard: ffprobe returned 0 = file is not a valid video + if duration <= 0: + logging.error( + f"❌ compress_video: ffprobe returned duration={duration} " + f"— file is not a valid video: {input_path} " + f"({os.path.getsize(input_path)} bytes)" + ) + return False + + trim_to = min(duration, max_duration) + target_bits = max_size_bytes * 8 * 0.90 target_kbps = int(target_bits / trim_to / 1000) - video_kbps = max(200, target_kbps - 128) # reserve 128 k for audio + video_kbps = max(200, target_kbps - 128) logging.info( f"🎬 Compressing: duration={duration:.1f}s → trim={trim_to:.1f}s, " @@ -633,7 +638,10 @@ def download_video(url: str, output_path: str, def download_video_ytdlp(url: str, output_path: str, cookies: list = None) -> bool: - """Download a video using yt-dlp, optionally injecting cookies.""" + """ + Download a video using yt-dlp with TikTok impersonation support. + curl_cffi must be installed for impersonation to work. + """ cookie_file = None try: import yt_dlp @@ -644,9 +652,11 @@ def download_video_ytdlp(url: str, output_path: str, "quiet": True, "no_warnings": False, "merge_output_format": "mp4", + # ── TikTok impersonation ─────────────────────────────────── + # Requires curl_cffi: pip install curl-cffi + "impersonate": "chrome", } - # Write cookies to a temp Netscape file if provided if cookies: cookie_file = _write_netscape_cookies(cookies) if cookie_file: @@ -655,14 +665,21 @@ def download_video_ytdlp(url: str, output_path: str, with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download([url]) - if os.path.exists(output_path) and os.path.getsize(output_path) > 10_000: - logging.info( - f"✅ yt-dlp download OK: " - f"{os.path.getsize(output_path) / 1024 / 1024:.1f} MB" + # Validate: must exist AND be a real video (> 50 KB) + if os.path.exists(output_path): + size = os.path.getsize(output_path) + if size > 50_000: + logging.info( + f"✅ yt-dlp download OK: {size / 1024 / 1024:.1f} MB" + ) + return True + logging.error( + f"❌ yt-dlp output too small ({size} bytes) — " + f"likely an HTML error page, not a video." ) - return True + return False - logging.error("❌ yt-dlp produced no output file or file too small.") + logging.error("❌ yt-dlp produced no output file.") return False except Exception as e: @@ -672,7 +689,6 @@ def download_video_ytdlp(url: str, output_path: str, if cookie_file and os.path.exists(cookie_file): os.unlink(cookie_file) - def _write_netscape_cookies(cookies: list) -> str | None: """Write cookies list to a Netscape-format temp file for yt-dlp.""" try: