try
This commit is contained in:
@@ -23,31 +23,69 @@ pipeline {
|
|||||||
stage('Setup Python & Install Dependencies') {
|
stage('Setup Python & Install Dependencies') {
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -e # Exit immediately if a command exits with a non-zero status
|
set -e
|
||||||
|
|
||||||
# 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; }
|
|
||||||
|
|
||||||
# Verify that moviepy can be imported
|
# ── Create venv ────────────────────────────────────
|
||||||
python3 -c "import moviepy" || { echo 'MoviePy import failed!'; exit 1; }
|
python3 -m venv venv
|
||||||
|
|
||||||
# Install the local browser binaries for this environment
|
# ── Upgrade pip toolchain ──────────────────────────
|
||||||
playwright install chromium
|
. 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') {
|
stage('Run Script') {
|
||||||
steps {
|
steps {
|
||||||
// Securely injects Jenkins credentials as environment variables
|
// Securely injects Jenkins credentials as environment variables
|
||||||
|
|||||||
@@ -542,18 +542,23 @@ def get_video_duration(path: str) -> float:
|
|||||||
def compress_video(input_path: str, output_path: str,
|
def compress_video(input_path: str, output_path: str,
|
||||||
max_duration: int = VIDEO_MAX_DURATION_S,
|
max_duration: int = VIDEO_MAX_DURATION_S,
|
||||||
max_size_bytes: int = VIDEO_MAX_SIZE_BYTES) -> bool:
|
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:
|
try:
|
||||||
duration = get_video_duration(input_path)
|
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_bits = max_size_bytes * 8 * 0.90
|
||||||
target_kbps = int(target_bits / trim_to / 1000)
|
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(
|
logging.info(
|
||||||
f"🎬 Compressing: duration={duration:.1f}s → trim={trim_to:.1f}s, "
|
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,
|
def download_video_ytdlp(url: str, output_path: str,
|
||||||
cookies: list = None) -> bool:
|
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
|
cookie_file = None
|
||||||
try:
|
try:
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
@@ -644,9 +652,11 @@ def download_video_ytdlp(url: str, output_path: str,
|
|||||||
"quiet": True,
|
"quiet": True,
|
||||||
"no_warnings": False,
|
"no_warnings": False,
|
||||||
"merge_output_format": "mp4",
|
"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:
|
if cookies:
|
||||||
cookie_file = _write_netscape_cookies(cookies)
|
cookie_file = _write_netscape_cookies(cookies)
|
||||||
if cookie_file:
|
if cookie_file:
|
||||||
@@ -655,14 +665,21 @@ def download_video_ytdlp(url: str, output_path: str,
|
|||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
ydl.download([url])
|
ydl.download([url])
|
||||||
|
|
||||||
if os.path.exists(output_path) and os.path.getsize(output_path) > 10_000:
|
# Validate: must exist AND be a real video (> 50 KB)
|
||||||
logging.info(
|
if os.path.exists(output_path):
|
||||||
f"✅ yt-dlp download OK: "
|
size = os.path.getsize(output_path)
|
||||||
f"{os.path.getsize(output_path) / 1024 / 1024:.1f} MB"
|
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
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
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):
|
if cookie_file and os.path.exists(cookie_file):
|
||||||
os.unlink(cookie_file)
|
os.unlink(cookie_file)
|
||||||
|
|
||||||
|
|
||||||
def _write_netscape_cookies(cookies: list) -> str | None:
|
def _write_netscape_cookies(cookies: list) -> str | None:
|
||||||
"""Write cookies list to a Netscape-format temp file for yt-dlp."""
|
"""Write cookies list to a Netscape-format temp file for yt-dlp."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user