- Restructured docker-compose for Nginx Proxy Manager (no custom nginx) - Added domain wordly.art configuration - Added Prometheus + Grafana monitoring stack with pre-configured dashboards - Added PostgreSQL backup script to NAS (daily/weekly/monthly rotation) - Added alert rules for backend, system, and Docker metrics - Updated deployment guide for NPM + IONOS DNS homelab setup - Added marketing plan document - PDF translator and watermark support - Enhanced middleware, routes, and translator modules Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
169 lines
5.2 KiB
Python
169 lines
5.2 KiB
Python
"""
|
|
Watermark module for Free-tier output files.
|
|
|
|
Strategy: Add a subtle, professional footer/header that:
|
|
- Clearly marks the file as "Free plan" output
|
|
- Doesn't destroy the document's usability
|
|
- Motivates upgrade without frustrating the user
|
|
|
|
DOCX: Footer with light-gray text + link
|
|
PPTX: Small text box bottom-right on each slide
|
|
XLSX: Footer text on each sheet
|
|
"""
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from docx import Document
|
|
from docx.oxml import OxmlElement
|
|
from docx.oxml.ns import qn
|
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
|
|
from core.logging import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
WATERMARK_TEXT = "Translated with Office Translator — Free Plan"
|
|
WATERMARK_URL = "wordly.art"
|
|
WATERMARK_COLOR = "B0B0B0" # Light gray
|
|
|
|
|
|
def add_watermark_docx(output_path: Path) -> None:
|
|
"""Add a subtle footer watermark to a DOCX file."""
|
|
try:
|
|
document = Document(str(output_path))
|
|
|
|
for section in document.sections:
|
|
# Enable footer
|
|
footer = section.footer
|
|
footer.is_linked_to_previous = False
|
|
|
|
if not footer.paragraphs:
|
|
footer.add_paragraph()
|
|
|
|
para = footer.paragraphs[0]
|
|
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
|
# Clear existing content
|
|
for run in para.runs:
|
|
run.clear()
|
|
|
|
# Add watermark run
|
|
run = para.add_run(WATERMARK_TEXT)
|
|
run.font.size = 8 # Pt — small
|
|
run.font.color.rgb = None # Will set via XML
|
|
|
|
# Set color via XML (more reliable)
|
|
rPr = run._r.get_or_add_rPr()
|
|
color_elem = OxmlElement("w:color")
|
|
color_elem.set(qn("w:val"), WATERMARK_COLOR)
|
|
rPr.append(color_elem)
|
|
|
|
document.save(str(output_path))
|
|
logger.info("watermark_added", file=str(output_path))
|
|
|
|
except Exception as e:
|
|
logger.error("watermark_docx_error", error=str(e))
|
|
# Don't fail the whole translation if watermark fails
|
|
|
|
|
|
def add_watermark_pptx(output_path: Path) -> None:
|
|
"""Add a small watermark text box to each slide."""
|
|
try:
|
|
from pptx import Presentation
|
|
from pptx.util import Inches, Pt, Emu
|
|
from pptx.dml.color import RGBColor
|
|
from pptx.enum.text import PP_ALIGN
|
|
|
|
prs = Presentation(str(output_path))
|
|
|
|
for slide in prs.slides:
|
|
# Add text box at bottom-right
|
|
left = Inches(5.5)
|
|
top = Inches(7.0)
|
|
width = Inches(3.5)
|
|
height = Inches(0.4)
|
|
|
|
txBox = slide.shapes.add_textbox(left, top, width, height)
|
|
tf = txBox.text_frame
|
|
tf.word_wrap = True
|
|
|
|
p = tf.paragraphs[0]
|
|
p.alignment = PP_ALIGN.RIGHT
|
|
run = p.add_run()
|
|
run.text = WATERMARK_TEXT
|
|
run.font.size = Pt(7)
|
|
run.font.color.rgb = RGBColor(0xB0, 0xB0, 0xB0)
|
|
|
|
prs.save(str(output_path))
|
|
logger.info("watermark_added_pptx", file=str(output_path))
|
|
|
|
except Exception as e:
|
|
logger.error("watermark_pptx_error", error=str(e))
|
|
|
|
|
|
def add_watermark_xlsx(output_path: Path) -> None:
|
|
"""Add a footer watermark to each sheet."""
|
|
try:
|
|
from openpyxl import load_workbook
|
|
from openpyxl.worksheet.header_footer import HeaderFooter
|
|
|
|
wb = load_workbook(str(output_path))
|
|
|
|
for ws in wb.worksheets:
|
|
if ws.sheet_properties.pageSetUpPr is None:
|
|
from openpyxl.worksheet.properties import PageSetupProperties
|
|
ws.sheet_properties.pageSetUpPr = PageSetupProperties()
|
|
|
|
ws.oddFooter.center.text = WATERMARK_TEXT
|
|
ws.oddFooter.center.font = "Arial,Regular"
|
|
ws.oddFooter.center.size = 8
|
|
|
|
wb.save(str(output_path))
|
|
wb.close()
|
|
logger.info("watermark_added_xlsx", file=str(output_path))
|
|
|
|
except Exception as e:
|
|
logger.error("watermark_xlsx_error", error=str(e))
|
|
|
|
|
|
def add_watermark_pdf(output_path: Path) -> None:
|
|
"""Add a subtle footer watermark on every page of a PDF using PyMuPDF."""
|
|
try:
|
|
import fitz
|
|
|
|
doc = fitz.open(str(output_path))
|
|
for page in doc:
|
|
rect = page.rect
|
|
# Position at bottom center
|
|
text_point = fitz.Point(
|
|
rect.width / 2 - 100,
|
|
rect.height - 15
|
|
)
|
|
page.insert_text(
|
|
text_point,
|
|
WATERMARK_TEXT,
|
|
fontsize=7,
|
|
color=(0.69, 0.69, 0.69), # #B0B0B0
|
|
fontname="helv",
|
|
)
|
|
doc.save(str(output_path), incremental=True, encryption=0)
|
|
doc.close()
|
|
logger.info("watermark_added_pdf", file=str(output_path))
|
|
except Exception as e:
|
|
logger.error("watermark_pdf_error", error=str(e))
|
|
|
|
|
|
def add_watermark(output_path: Path, file_extension: str) -> None:
|
|
"""Apply watermark based on file type."""
|
|
ext = file_extension.lower()
|
|
if ext == ".docx":
|
|
add_watermark_docx(output_path)
|
|
elif ext == ".pptx":
|
|
add_watermark_pptx(output_path)
|
|
elif ext == ".xlsx":
|
|
add_watermark_xlsx(output_path)
|
|
elif ext == ".pdf":
|
|
add_watermark_pdf(output_path)
|
|
else:
|
|
logger.info("watermark_skipped", extension=ext)
|