# Pages with a vertical height longer than this will use the 'stitch together' method. # - Many GPUs have a max texture size of 16384x16384px (or lower on older devices). # - If a page is taller than ~8000–10000px, it risks exceeding GPU memory limits. # - This is especially important on headless Chromium, where Playwright may fail to allocate a massive full-page buffer. from loguru import logger from changedetectionio.content_fetchers import SCREENSHOT_MAX_HEIGHT_DEFAULT, SCREENSHOT_DEFAULT_QUALITY def stitch_images_worker(pipe_conn, chunks_bytes, original_page_height, capture_height): import os import io from PIL import Image, ImageDraw, ImageFont try: # Load images from byte chunks images = [Image.open(io.BytesIO(b)) for b in chunks_bytes] total_height = sum(im.height for im in images) max_width = max(im.width for im in images) # Create stitched image stitched = Image.new('RGB', (max_width, total_height)) y_offset = 0 for im in images: stitched.paste(im, (0, y_offset)) y_offset += im.height # Draw caption on top (overlaid, not extending canvas) draw = ImageDraw.Draw(stitched) if original_page_height > capture_height: caption_text = f"WARNING: Screenshot was {original_page_height}px but trimmed to {capture_height}px because it was too long" padding = 10 font_size = 35 font_color = (255, 0, 0) background_color = (255, 255, 255) # Try to load a proper font try: font = ImageFont.truetype("arial.ttf", font_size) except IOError: font = ImageFont.load_default() bbox = draw.textbbox((0, 0), caption_text, font=font) text_width = bbox[2] - bbox[0] text_height = bbox[3] - bbox[1] # Draw white rectangle background behind text rect_top = 0 rect_bottom = text_height + 2 * padding draw.rectangle([(0, rect_top), (max_width, rect_bottom)], fill=background_color) # Draw text centered horizontally, 10px padding from top of the rectangle text_x = (max_width - text_width) // 2 text_y = padding draw.text((text_x, text_y), caption_text, font=font, fill=font_color) # Encode and send image output = io.BytesIO() stitched.save(output, format="JPEG", quality=int(os.getenv("SCREENSHOT_QUALITY", SCREENSHOT_DEFAULT_QUALITY))) pipe_conn.send_bytes(output.getvalue()) stitched.close() except Exception as e: pipe_conn.send(f"error:{e}") finally: pipe_conn.close()