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'])