Fix cover page: underline only 'Disclaimer', ensure page break, add SERVER_TIMEZONE env var
This commit is contained in:
@@ -36,6 +36,9 @@ ENV CELERY_BROKER_URL=redis://localhost:6379/0
|
|||||||
ENV CELERY_RESULT_BACKEND=redis://localhost:6379/0
|
ENV CELERY_RESULT_BACKEND=redis://localhost:6379/0
|
||||||
ENV SCRAIBE_UPLOAD_DIR=/tmp/scraibe_uploads
|
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
|
# Email and template configuration
|
||||||
ENV EMAIL_CONTACT_ADDRESS=support@example.com
|
ENV EMAIL_CONTACT_ADDRESS=support@example.com
|
||||||
ENV EMAIL_CSS_PATH=
|
ENV EMAIL_CSS_PATH=
|
||||||
|
|||||||
+30
-41
@@ -3,27 +3,24 @@ Cover-page generator for transcript and summary DOCX files.
|
|||||||
|
|
||||||
Layout (all centered):
|
Layout (all centered):
|
||||||
- 5 blank lines
|
- 5 blank lines
|
||||||
- Logo from COVER_PAGE_LOGO_URL (if set)
|
|
||||||
- 3 blank lines
|
- 3 blank lines
|
||||||
- Title in uppercase: "TRANSCRIPT" or "SUMMARY"
|
- Title in uppercase: "TRANSCRIPT" or "SUMMARY"
|
||||||
- Next line: date of transcription (e.g. "June 19, 2026")
|
- Next line: date of transcription (e.g. "June 19, 2026")
|
||||||
- Next line: "Created by <COVER_PAGE_ORGANIZATION>"
|
- Next line: "Created by <COVER_PAGE_ORGANIZATION>"
|
||||||
- 3 blank lines
|
- 3 blank lines
|
||||||
- Disclaimer text
|
- Disclaimer (underlined)
|
||||||
|
- Page break (ensures content starts on page 2)
|
||||||
A page break is inserted after the cover page so the main content starts on page 2.
|
|
||||||
|
|
||||||
Environment variables:
|
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_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 os
|
||||||
import io
|
|
||||||
import httpx
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
from docx import Document
|
from docx import Document
|
||||||
from docx.shared import Pt
|
from docx.shared import Pt
|
||||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||||
@@ -31,7 +28,15 @@ 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):
|
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."""
|
"""Add a centered paragraph with given text and style."""
|
||||||
p = doc.add_paragraph()
|
p = doc.add_paragraph()
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
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.name = "Courier"
|
||||||
run.font.size = font_size
|
run.font.size = font_size
|
||||||
run.bold = bold
|
run.bold = bold
|
||||||
|
run.underline = underline
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
@@ -50,9 +56,10 @@ def _add_blank_lines(doc: Document, count: int):
|
|||||||
|
|
||||||
|
|
||||||
def _add_page_break(doc: Document):
|
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()
|
p = doc.add_paragraph()
|
||||||
pPr = p._p.get_or_add_pPr()
|
pPr = p._p.get_or_add_pPr()
|
||||||
|
# Clean existing formatting
|
||||||
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"):
|
||||||
@@ -62,17 +69,6 @@ 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:
|
|
||||||
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(
|
def add_cover_page(
|
||||||
doc: Document,
|
doc: Document,
|
||||||
title: str,
|
title: str,
|
||||||
@@ -83,35 +79,23 @@ def add_cover_page(
|
|||||||
|
|
||||||
- title: e.g., "TRANSCRIPT" or "SUMMARY"
|
- title: e.g., "TRANSCRIPT" or "SUMMARY"
|
||||||
- date_str: optional date string (e.g., "June 19, 2026");
|
- 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
|
# 5 blank lines
|
||||||
_add_blank_lines(doc, 5)
|
_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
|
# 3 blank lines
|
||||||
_add_blank_lines(doc, 3)
|
_add_blank_lines(doc, 3)
|
||||||
|
|
||||||
# Title in uppercase
|
# Title in uppercase
|
||||||
_add_centered_paragraph(doc, text=title.upper().strip(), font_size=Pt(18), bold=True)
|
_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:
|
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))
|
_add_centered_paragraph(doc, text=date_str, font_size=Pt(14))
|
||||||
|
|
||||||
# Created by + organization
|
# Created by + organization
|
||||||
@@ -122,12 +106,17 @@ def add_cover_page(
|
|||||||
# 3 blank lines
|
# 3 blank lines
|
||||||
_add_blank_lines(doc, 3)
|
_add_blank_lines(doc, 3)
|
||||||
|
|
||||||
# Disclaimer
|
# Disclaimer: only the word "Disclaimer" is underlined
|
||||||
disclaimer = (
|
disclaimer_title = "Disclaimer"
|
||||||
"Disclaimer\n"
|
disclaimer_body = (
|
||||||
"This transcription and any summary derived therefrom may contain errors or inaccuracies."
|
"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
|
# Page break after cover page so main content starts on page 2
|
||||||
_add_page_break(doc)
|
_add_page_break(doc)
|
||||||
|
|||||||
Reference in New Issue
Block a user