Redesign cover page layout for transcript and summary DOCX
Mirror and run GitLab CI / build (push) Has been cancelled
Ruff / ruff (push) Has been cancelled

- Remove old title-only style.
- New layout (centered):
  - 5 blank lines
  - Logo from COVER_PAGE_LOGO_URL
  - 3 blank lines
  - Title (TRANSCRIPT or SUMMARY)
  - Date of transcription
  - Created by + COVER_PAGE_ORGANIZATION
  - 3 blank lines
  - Disclaimer
- Insert page break after cover page; content starts on page 2.
This commit is contained in:
admin
2026-06-20 02:20:28 +00:00
parent 63832d01d3
commit 0a038d9e74
2 changed files with 97 additions and 82 deletions
+91 -76
View File
@@ -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 <COVER_PAGE_ORGANIZATION>"
- 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)
+6 -6
View File
@@ -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