diff --git a/scraibe/docx_cover.py b/scraibe/docx_cover.py index 40cd498..ac429d6 100644 --- a/scraibe/docx_cover.py +++ b/scraibe/docx_cover.py @@ -1,28 +1,58 @@ """ -Reusable cover-page generator for transcript and summary DOCX files. +Cover-page generator for transcript and summary DOCX files. -Configuration (env): +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. + +Environment variables: - COVER_PAGE_ENABLED: "true"/"false" (default: false) - COVER_PAGE_ORGANIZATION: e.g., "A.P.Strom" -- COVER_PAGE_TITLE_PREFIX: e.g., "TRANSCRIPT" or "SUMMARY" -- COVER_PAGE_LOGO_URL: optional URL -- COVER_PAGE_LOGO_PATH: optional local path +- COVER_PAGE_LOGO_URL: URL of logo image to include on the cover page """ import os +import io +import requests +from datetime import datetime from typing import Optional from docx import Document -from docx.shared import Pt, Inches +from docx.shared import Pt from docx.enum.text import WD_ALIGN_PARAGRAPH 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): + """Add a centered paragraph with given text and style.""" + p = doc.add_paragraph() + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + if text: + run = p.add_run(text) + run.font.name = "Courier" + run.font.size = font_size + run.bold = bold + return p + + +def _add_blank_lines(doc: Document, count: int): + """Add specified number of blank lines (empty centered paragraphs).""" + for _ in range(count): + _add_centered_paragraph(doc) + + def _add_page_break(doc: Document): """Insert a page break paragraph.""" p = doc.add_paragraph() pPr = p._p.get_or_add_pPr() - # Clear spacing/tabs for child in list(pPr): tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag if tag in ("tabs", "spacing", "ind"): @@ -32,87 +62,72 @@ 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: + r = requests.get(url, timeout=10) + if r.status_code == 200: + return r.content + except Exception: + pass + return None + + def add_cover_page( doc: Document, title: str, - subtitle: Optional[str] = None, - metadata: Optional[dict] = None, - include_logo: bool = False, + date_str: Optional[str] = None, ): """ Insert a cover page at the current cursor position. - title: e.g., "TRANSCRIPT" or "SUMMARY" - - subtitle: e.g., "Meeting of 16 June 2026" - - metadata: optional dict with keys like: - - "Organization" - - "Date" - - "Prepared by" - - "Reference" + - date_str: optional date string (e.g., "June 19, 2026"); + if not provided, uses today's date. """ - org = (os.getenv("COVER_PAGE_ORGANIZATION") or "").strip() or metadata.get("Organization") if metadata else None - date = (metadata.get("Date") if metadata else None) or "" - prepared_by = (metadata.get("Prepared by") if metadata else None) or "" - reference = (metadata.get("Reference") if metadata else None) or "" + # 5 blank lines + _add_blank_lines(doc, 5) - # Title - p = doc.add_paragraph() - p.alignment = WD_ALIGN_PARAGRAPH.CENTER - p.paragraph_format.space_after = Pt(6) - run = p.add_run(title.upper()) - run.bold = True - run.font.name = "Courier" - run.font.size = Pt(18) - - # Subtitle - if subtitle: - p = doc.add_paragraph() - p.alignment = WD_ALIGN_PARAGRAPH.CENTER - p.paragraph_format.space_after = Pt(12) - run = p.add_run(subtitle) - run.font.name = "Courier" - run.font.size = Pt(14) - - # Optional logo placeholder (text-only for now; can be extended) - if include_logo: - logo_url = (os.getenv("COVER_PAGE_LOGO_URL") or "").strip() - logo_path = (os.getenv("COVER_PAGE_LOGO_PATH") or "").strip() - # For now, just reserve space; image insertion can be added later. - p = doc.add_paragraph() - p.alignment = WD_ALIGN_PARAGRAPH.CENTER - p.paragraph_format.space_after = Pt(12) - - # Metadata lines - if org or date or prepared_by or reference: - p = doc.add_paragraph() - p.alignment = WD_ALIGN_PARAGRAPH.CENTER - p.paragraph_format.space_after = Pt(4) - if org: - r = p.add_run(org) - r.font.name = "Courier" - r.font.size = Pt(12) - if date: - if org: - p.add_run("\n") - r = p.add_run(date) - r.font.name = "Courier" - r.font.size = Pt(12) - - if prepared_by or reference: + # 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 - p.paragraph_format.space_after = Pt(4) - if prepared_by: - r = p.add_run(f"Prepared by: {prepared_by}") - r.font.name = "Courier" - r.font.size = Pt(11) - if reference: - if prepared_by: - p.add_run("\n") - r = p.add_run(f"Reference: {reference}") - r.font.name = "Courier" - r.font.size = Pt(11) + try: + # Add image centered + p.add_run().add_picture(io.BytesIO(img_bytes), width=None) + except Exception: + # Fallback: do nothing if image fails + pass - # Page break after cover page + # 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 + if not date_str: + date_str = datetime.utcnow().strftime("%B %d, %Y") + _add_centered_paragraph(doc, text=date_str, font_size=Pt(14)) + + # Created by + organization + org = (os.getenv("COVER_PAGE_ORGANIZATION") or "").strip() + if org: + _add_centered_paragraph(doc, text=f"Created by {org}", font_size=Pt(12)) + + # 3 blank lines + _add_blank_lines(doc, 3) + + # Disclaimer + disclaimer = ( + "Disclaimer\n" + "This transcription and any summary derived therefrom may contain errors or inaccuracies." + ) + _add_centered_paragraph(doc, text=disclaimer, font_size=Pt(10)) + + # Page break after cover page so main content starts on page 2 _add_page_break(doc) diff --git a/scraibe/email_sender.py b/scraibe/email_sender.py index 54f29c8..15aa808 100644 --- a/scraibe/email_sender.py +++ b/scraibe/email_sender.py @@ -507,14 +507,14 @@ def create_transcript_docx(text: str, filename: str): # Step 3: Optionally add cover page from . import docx_cover + from datetime import datetime cover_enabled = os.getenv("COVER_PAGE_ENABLED", "false").strip().lower() in ("true", "1", "yes") if cover_enabled: + date_str = datetime.utcnow().strftime("%B %d, %Y") docx_cover.add_cover_page( doc, title="TRANSCRIPT", - subtitle=None, - metadata=None, - include_logo=True, + date_str=date_str, ) # Step 4: Write prepared pages into DOCX @@ -600,14 +600,14 @@ def create_summary_docx(text: str, filename: str): # Optionally add cover page from . import docx_cover + from datetime import datetime cover_enabled = os.getenv("COVER_PAGE_ENABLED", "false").strip().lower() in ("true", "1", "yes") if cover_enabled: + date_str = datetime.utcnow().strftime("%B %d, %Y") docx_cover.add_cover_page( doc, title="SUMMARY", - subtitle=None, - metadata=None, - include_logo=True, + date_str=date_str, ) # Add summary content using markdown-aware styling