import os from PIL import Image from html2image import Html2Image import numpy as np from djagitit.mailing import html import uuid from config import emailParams from contextlib import contextmanager @contextmanager def silence_stderr_fd(): """ Silence C-level stderr (fd 2), so subprocesses like Chrome can't write their DBus / GL / GPU spam. """ # Duplicate current stderr fd saved_stderr_fd = os.dup(2) devnull_fd = None try: # Open /dev/null and dup it over fd 2 devnull_fd = os.open(os.devnull, os.O_WRONLY) os.dup2(devnull_fd, 2) os.close(devnull_fd) devnull_fd = None # Mark as closed yield finally: # Ensure devnull_fd is closed if dup2 failed if devnull_fd is not None: try: os.close(devnull_fd) except OSError: pass # Restore original stderr os.dup2(saved_stderr_fd, 2) os.close(saved_stderr_fd) def generate_img64_from_html( html_str, tmp_path=os.path.join(os.path.dirname(__file__), '..', 'tmp'), width=600 ): random_id = str(uuid.uuid4()) raw_path = os.path.join(tmp_path, f'{random_id}_raw.png') cropped_path = os.path.join(tmp_path, f'{random_id}_cropped.png') try: hti = Html2Image(output_path=tmp_path) with silence_stderr_fd(): hti.screenshot(html_str=html_str, save_as=f'{random_id}_raw.png') img = np.array(Image.open(raw_path)) # img is 1080 rows by 1920 cols and 4 color channels, the 4'th channel is alpha. idx = np.where(img[:, :, 3] > 0) # Find indices of non-transparent pixels (indices where alpha channel value is above zero). x0, y0, x1, y1 = idx[1].min(), idx[0].min(), idx[1].max(), idx[0].max() # Get minimum and maximum index in both axes (top left corner and bottom right corner) img = Image.fromarray(img[y0:y1+1, x0:x1+1, :]) # Crop rectangle and convert to Image img.save(cropped_path) # save cropped image img64 = html.image_to_base64(cropped_path, width=width) return img64 finally: # Ensure temporary files are always cleaned up, even if an exception occurs for file_path in [raw_path, cropped_path]: try: if os.path.exists(file_path): os.remove(file_path) except OSError: # Ignore errors during cleanup (e.g., file already deleted) pass def get_subject(mode): return emailParams.subjects.get(mode, emailParams.subjects['daily'])