Redesign cover page layout for transcript and summary DOCX
- 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:
+91
-76
@@ -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_ENABLED: "true"/"false" (default: false)
|
||||||
- COVER_PAGE_ORGANIZATION: e.g., "A.P.Strom"
|
- COVER_PAGE_ORGANIZATION: e.g., "A.P.Strom"
|
||||||
- COVER_PAGE_TITLE_PREFIX: e.g., "TRANSCRIPT" or "SUMMARY"
|
- COVER_PAGE_LOGO_URL: URL of logo image to include on the cover page
|
||||||
- COVER_PAGE_LOGO_URL: optional URL
|
|
||||||
- COVER_PAGE_LOGO_PATH: optional local path
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import io
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from docx import Document
|
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.enum.text import WD_ALIGN_PARAGRAPH
|
||||||
from docx.oxml import OxmlElement
|
from docx.oxml import OxmlElement
|
||||||
from docx.oxml.ns import qn
|
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):
|
def _add_page_break(doc: Document):
|
||||||
"""Insert a page break paragraph."""
|
"""Insert a page break paragraph."""
|
||||||
p = doc.add_paragraph()
|
p = doc.add_paragraph()
|
||||||
pPr = p._p.get_or_add_pPr()
|
pPr = p._p.get_or_add_pPr()
|
||||||
# Clear spacing/tabs
|
|
||||||
for child in list(pPr):
|
for child in list(pPr):
|
||||||
tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag
|
tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag
|
||||||
if tag in ("tabs", "spacing", "ind"):
|
if tag in ("tabs", "spacing", "ind"):
|
||||||
@@ -32,87 +62,72 @@ def _add_page_break(doc: Document):
|
|||||||
pPr.append(page_break)
|
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(
|
def add_cover_page(
|
||||||
doc: Document,
|
doc: Document,
|
||||||
title: str,
|
title: str,
|
||||||
subtitle: Optional[str] = None,
|
date_str: Optional[str] = None,
|
||||||
metadata: Optional[dict] = None,
|
|
||||||
include_logo: bool = False,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Insert a cover page at the current cursor position.
|
Insert a cover page at the current cursor position.
|
||||||
|
|
||||||
- title: e.g., "TRANSCRIPT" or "SUMMARY"
|
- title: e.g., "TRANSCRIPT" or "SUMMARY"
|
||||||
- subtitle: e.g., "Meeting of 16 June 2026"
|
- date_str: optional date string (e.g., "June 19, 2026");
|
||||||
- metadata: optional dict with keys like:
|
if not provided, uses today's date.
|
||||||
- "Organization"
|
|
||||||
- "Date"
|
|
||||||
- "Prepared by"
|
|
||||||
- "Reference"
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
org = (os.getenv("COVER_PAGE_ORGANIZATION") or "").strip() or metadata.get("Organization") if metadata else None
|
# 5 blank lines
|
||||||
date = (metadata.get("Date") if metadata else None) or ""
|
_add_blank_lines(doc, 5)
|
||||||
prepared_by = (metadata.get("Prepared by") if metadata else None) or ""
|
|
||||||
reference = (metadata.get("Reference") if metadata else None) or ""
|
|
||||||
|
|
||||||
# Title
|
# Logo (if COVER_PAGE_LOGO_URL is set)
|
||||||
p = doc.add_paragraph()
|
logo_url = (os.getenv("COVER_PAGE_LOGO_URL") or "").strip()
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
if logo_url:
|
||||||
p.paragraph_format.space_after = Pt(6)
|
img_bytes = _download_image_bytes(logo_url)
|
||||||
run = p.add_run(title.upper())
|
if img_bytes:
|
||||||
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:
|
|
||||||
p = doc.add_paragraph()
|
p = doc.add_paragraph()
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
p.paragraph_format.space_after = Pt(4)
|
try:
|
||||||
if prepared_by:
|
# Add image centered
|
||||||
r = p.add_run(f"Prepared by: {prepared_by}")
|
p.add_run().add_picture(io.BytesIO(img_bytes), width=None)
|
||||||
r.font.name = "Courier"
|
except Exception:
|
||||||
r.font.size = Pt(11)
|
# Fallback: do nothing if image fails
|
||||||
if reference:
|
pass
|
||||||
if prepared_by:
|
|
||||||
p.add_run("\n")
|
|
||||||
r = p.add_run(f"Reference: {reference}")
|
|
||||||
r.font.name = "Courier"
|
|
||||||
r.font.size = Pt(11)
|
|
||||||
|
|
||||||
# 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)
|
_add_page_break(doc)
|
||||||
|
|||||||
@@ -507,14 +507,14 @@ def create_transcript_docx(text: str, filename: str):
|
|||||||
|
|
||||||
# Step 3: Optionally add cover page
|
# Step 3: Optionally add cover page
|
||||||
from . import docx_cover
|
from . import docx_cover
|
||||||
|
from datetime import datetime
|
||||||
cover_enabled = os.getenv("COVER_PAGE_ENABLED", "false").strip().lower() in ("true", "1", "yes")
|
cover_enabled = os.getenv("COVER_PAGE_ENABLED", "false").strip().lower() in ("true", "1", "yes")
|
||||||
if cover_enabled:
|
if cover_enabled:
|
||||||
|
date_str = datetime.utcnow().strftime("%B %d, %Y")
|
||||||
docx_cover.add_cover_page(
|
docx_cover.add_cover_page(
|
||||||
doc,
|
doc,
|
||||||
title="TRANSCRIPT",
|
title="TRANSCRIPT",
|
||||||
subtitle=None,
|
date_str=date_str,
|
||||||
metadata=None,
|
|
||||||
include_logo=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 4: Write prepared pages into DOCX
|
# Step 4: Write prepared pages into DOCX
|
||||||
@@ -600,14 +600,14 @@ def create_summary_docx(text: str, filename: str):
|
|||||||
|
|
||||||
# Optionally add cover page
|
# Optionally add cover page
|
||||||
from . import docx_cover
|
from . import docx_cover
|
||||||
|
from datetime import datetime
|
||||||
cover_enabled = os.getenv("COVER_PAGE_ENABLED", "false").strip().lower() in ("true", "1", "yes")
|
cover_enabled = os.getenv("COVER_PAGE_ENABLED", "false").strip().lower() in ("true", "1", "yes")
|
||||||
if cover_enabled:
|
if cover_enabled:
|
||||||
|
date_str = datetime.utcnow().strftime("%B %d, %Y")
|
||||||
docx_cover.add_cover_page(
|
docx_cover.add_cover_page(
|
||||||
doc,
|
doc,
|
||||||
title="SUMMARY",
|
title="SUMMARY",
|
||||||
subtitle=None,
|
date_str=date_str,
|
||||||
metadata=None,
|
|
||||||
include_logo=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add summary content using markdown-aware styling
|
# Add summary content using markdown-aware styling
|
||||||
|
|||||||
Reference in New Issue
Block a user