diff --git a/Dockerfile b/Dockerfile index e7f3574..1eac48a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,6 +36,9 @@ ENV CELERY_BROKER_URL=redis://localhost:6379/0 ENV CELERY_RESULT_BACKEND=redis://localhost:6379/0 ENV SCRAIBE_UPLOAD_DIR=/tmp/scraibe_uploads +# Server timezone for document dates (e.g., America/Toronto, Europe/Berlin) +ENV SERVER_TIMEZONE=UTC + # Email and template configuration ENV EMAIL_CONTACT_ADDRESS=support@example.com ENV EMAIL_CSS_PATH= diff --git a/scraibe/docx_cover.py b/scraibe/docx_cover.py index 55634c0..0ca9c35 100644 --- a/scraibe/docx_cover.py +++ b/scraibe/docx_cover.py @@ -3,27 +3,24 @@ Cover-page generator for transcript and summary DOCX files. Layout (all centered): - 5 blank lines -- Logo from COVER_PAGE_LOGO_URL (if set) - 3 blank lines - Title in uppercase: "TRANSCRIPT" or "SUMMARY" - Next line: date of transcription (e.g. "June 19, 2026") - Next line: "Created by " - 3 blank lines -- Disclaimer text - -A page break is inserted after the cover page so the main content starts on page 2. +- Disclaimer (underlined) +- Page break (ensures content starts on page 2) Environment variables: - COVER_PAGE_ENABLED: "true"/"false" (default: false) - COVER_PAGE_ORGANIZATION: e.g., "A.P.Strom" -- COVER_PAGE_LOGO_URL: URL of logo image to include on the cover page +- SERVER_TIMEZONE: Timezone for document dates (e.g., "America/Toronto"). Defaults to "UTC". """ import os -import io -import httpx from datetime import datetime from typing import Optional +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from docx import Document from docx.shared import Pt from docx.enum.text import WD_ALIGN_PARAGRAPH @@ -31,7 +28,15 @@ from docx.oxml import OxmlElement from docx.oxml.ns import qn -def _add_centered_paragraph(doc: Document, text: str = "", font_size: Pt = Pt(12), bold: bool = False): +def _get_server_timezone(): + tz_name = (os.getenv("SERVER_TIMEZONE") or "UTC").strip() + try: + return ZoneInfo(tz_name) + except (ZoneInfoNotFoundError, KeyError, ValueError): + return ZoneInfo("UTC") + + +def _add_centered_paragraph(doc: Document, text: str = "", font_size: Pt = Pt(12), bold: bool = False, underline: bool = False): """Add a centered paragraph with given text and style.""" p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER @@ -40,6 +45,7 @@ def _add_centered_paragraph(doc: Document, text: str = "", font_size: Pt = Pt(12 run.font.name = "Courier" run.font.size = font_size run.bold = bold + run.underline = underline return p @@ -50,9 +56,10 @@ def _add_blank_lines(doc: Document, count: int): def _add_page_break(doc: Document): - """Insert a page break paragraph.""" + """Insert a page break paragraph to force next content to page 2.""" p = doc.add_paragraph() pPr = p._p.get_or_add_pPr() + # Clean existing formatting for child in list(pPr): tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag if tag in ("tabs", "spacing", "ind"): @@ -62,17 +69,6 @@ def _add_page_break(doc: Document): pPr.append(page_break) -def _download_image_bytes(url: str) -> Optional[bytes]: - """Download image from URL and return bytes, or None on failure.""" - try: - with httpx.stream("GET", url, timeout=10) as resp: - if resp.status_code == 200: - return resp.content - except Exception: - pass - return None - - def add_cover_page( doc: Document, title: str, @@ -83,35 +79,23 @@ def add_cover_page( - title: e.g., "TRANSCRIPT" or "SUMMARY" - date_str: optional date string (e.g., "June 19, 2026"); - if not provided, uses today's date. + if not provided, uses current server time in SERVER_TIMEZONE. """ # 5 blank lines _add_blank_lines(doc, 5) - # Logo (if COVER_PAGE_LOGO_URL is set) - logo_url = (os.getenv("COVER_PAGE_LOGO_URL") or "").strip() - if logo_url: - img_bytes = _download_image_bytes(logo_url) - if img_bytes: - p = doc.add_paragraph() - p.alignment = WD_ALIGN_PARAGRAPH.CENTER - try: - # Add image centered - p.add_run().add_picture(io.BytesIO(img_bytes), width=None) - except Exception: - # Fallback: do nothing if image fails - pass - # 3 blank lines _add_blank_lines(doc, 3) # Title in uppercase _add_centered_paragraph(doc, text=title.upper().strip(), font_size=Pt(18), bold=True) - # Date of transcription + # Date of transcription (use provided or current server time) if not date_str: - date_str = datetime.utcnow().strftime("%B %d, %Y") + tz = _get_server_timezone() + now = datetime.now(tz) + date_str = now.strftime("%B %d, %Y") _add_centered_paragraph(doc, text=date_str, font_size=Pt(14)) # Created by + organization @@ -122,12 +106,17 @@ def add_cover_page( # 3 blank lines _add_blank_lines(doc, 3) - # Disclaimer - disclaimer = ( - "Disclaimer\n" + # Disclaimer: only the word "Disclaimer" is underlined + disclaimer_title = "Disclaimer" + disclaimer_body = ( "This transcription and any summary derived therefrom may contain errors or inaccuracies." ) - _add_centered_paragraph(doc, text=disclaimer, font_size=Pt(10)) + + # Underlined "Disclaimer" line + p = _add_centered_paragraph(doc, text=disclaimer_title, font_size=Pt(10), underline=True) + + # Body text (not underlined) + _add_centered_paragraph(doc, text=disclaimer_body, font_size=Pt(10), underline=False) # Page break after cover page so main content starts on page 2 _add_page_break(doc)