import os
import re
import subprocess
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table,
TableStyle, HRFlowable, PageBreak, Image, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT
# Paths
DOCS_DIR = r"C:\Users\office\Documents\Final_year_project\fakeshield\docs"
MD_FILE = os.path.join(DOCS_DIR, "video_lab_documentation.md")
PDF_FILE = os.path.join(DOCS_DIR, "AI_Video_Lab_Technical_Specs.pdf")
MMDC_PATH = r"C:\Users\office\AppData\Roaming\npm\mmdc.ps1"
# Extract and compile Mermaid diagrams
print("Extracting Mermaid blocks from markdown...")
with open(MD_FILE, "r", encoding="utf-8") as f:
md_content = f.read()
mermaid_pattern = r"```mermaid\n(.*?)\n```"
mermaid_blocks = re.findall(mermaid_pattern, md_content, re.DOTALL)
png_diagrams = []
for idx, mmd_code in enumerate(mermaid_blocks):
temp_mmd = os.path.join(DOCS_DIR, f"temp_diag_vid_{idx}.mmd")
out_png = os.path.join(DOCS_DIR, f"video_lab_diagram_{idx}.png")
with open(temp_mmd, "w", encoding="utf-8") as f:
f.write(mmd_code)
print(f"Compiling Mermaid diagram {idx} to PNG...")
try:
# Run mmdc using powershell
cmd = ["powershell", "-File", MMDC_PATH, "-i", temp_mmd, "-o", out_png, "-t", "dark", "-b", "#0f172a"]
subprocess.run(cmd, check=True)
png_diagrams.append(out_png)
except Exception as e:
print(f"Error compiling diagram {idx}: {e}")
png_diagrams.append(None)
finally:
if os.path.exists(temp_mmd):
os.remove(temp_mmd)
# Set up ReportLab Document
doc = SimpleDocTemplate(
PDF_FILE, pagesize=A4,
rightMargin=1.5*cm, leftMargin=1.5*cm,
topMargin=2.0*cm, bottomMargin=2.0*cm
)
# Color Scheme
DARK = colors.HexColor("#0F172A") # Primary slate
CYAN = colors.HexColor("#00E5CC") # Accent cyan
GRAY = colors.HexColor("#64748B") # Muted text
LIGHT_BG = colors.HexColor("#F8FAFC") # Page backgrounds
BORDER = colors.HexColor("#E2E8F0") # Light border
ALERT_BG = colors.HexColor("#EFF6FF") # Alert callout bg
ALERT_BORDER = colors.HexColor("#3B82F6") # Alert callout border
styles = getSampleStyleSheet()
# Custom Paragraph Styles
styles.add(ParagraphStyle("DocTitle", parent=styles["Normal"], fontName="Helvetica-Bold", fontSize=22, leading=26, textColor=DARK, spaceAfter=15, alignment=TA_LEFT))
styles.add(ParagraphStyle("DocSubtitle", parent=styles["Normal"], fontName="Helvetica", fontSize=10, leading=14, textColor=GRAY, spaceAfter=30, alignment=TA_LEFT))
styles.add(ParagraphStyle("CustomH1", parent=styles["Heading1"], fontName="Helvetica-Bold", fontSize=13, leading=17, textColor=DARK, spaceBefore=22, spaceAfter=10, keepWithNext=True))
styles.add(ParagraphStyle("CustomH2", parent=styles["Heading2"], fontName="Helvetica-Bold", fontSize=11, leading=15, textColor=DARK, spaceBefore=16, spaceAfter=8, keepWithNext=True))
styles.add(ParagraphStyle("CustomH3", parent=styles["Heading3"], fontName="Helvetica-Bold", fontSize=9, leading=13, textColor=DARK, spaceBefore=12, spaceAfter=6, keepWithNext=True))
styles.add(ParagraphStyle("Body", parent=styles["Normal"], fontName="Helvetica", fontSize=8.5, leading=12.5, textColor=colors.HexColor("#334155"), spaceAfter=8))
styles.add(ParagraphStyle("ListItem", parent=styles["Normal"], fontName="Helvetica", fontSize=8.5, leading=12.5, textColor=colors.HexColor("#334155"), leftIndent=15, firstLineIndent=-10, spaceAfter=4))
styles.add(ParagraphStyle("CodeBlock", parent=styles["Normal"], fontName="Courier", fontSize=7.5, leading=10.5, textColor=colors.HexColor("#0F172A"), backColor=colors.HexColor("#F1F5F9"), borderColor=BORDER, borderWidth=0.5, borderPadding=8, spaceAfter=8, borderRadius=4))
styles.add(ParagraphStyle("TableText", parent=styles["Normal"], fontName="Helvetica", fontSize=8, leading=11, textColor=colors.HexColor("#334155")))
styles.add(ParagraphStyle("TableHeaderText", parent=styles["Normal"], fontName="Helvetica-Bold", fontSize=8, leading=11, textColor=colors.white))
def clean_markdown_formatting(text):
text = re.sub(r"\*\*(.*?)\*\*", r"\1", text)
text = re.sub(r"\*(.*?)\*", r"\1", text)
text = re.sub(r"`(.*?)`", r'\1', text)
# Basic math clean up for ReportLab
text = text.replace("$$\\text{Noise} = \\text{Frame}_{\\text{gray}} - \\text{GaussianFilter}(\\text{Frame}_{\\text{gray}}, \\sigma=2.0)$$".replace("\\", "\\"), "Noise = Frame_gray - GaussianFilter(Frame_gray, sigma=2.0)")
text = text.replace("$", "")
return text
# Build Story Flow
story = []
# Header
header_table = Table([[Paragraph("FAKESHIELD FORENSIC LABORATORIES", ParagraphStyle("HeaderL", parent=styles["Normal"], fontSize=9, textColor=GRAY, fontName="Helvetica-Bold")), Paragraph("VIDEO LAB TECHNICAL DOCUMENTATION", ParagraphStyle("HeaderR", parent=styles["Normal"], fontSize=9, textColor=GRAY, fontName="Helvetica-Bold", alignment=2))]], colWidths=[9*cm, 9*cm])
header_table.setStyle(TableStyle([("VALIGN", (0,0), (-1,-1), "MIDDLE"), ("BOTTOMPADDING", (0,0), (-1,-1), 4), ("TOPPADDING", (0,0), (-1,-1), 4)]))
story.append(header_table)
story.append(HRFlowable(width="100%", color=DARK, thickness=1.5, spaceAfter=15))
# Document Title
story.append(Paragraph("📹 AI Video Forensic Lab (Process 5.0)", styles["DocTitle"]))
story.append(Paragraph("System Architecture, Phased Pipeline, Forensic Model Ensemble, and Multi-modal Fusion", styles["DocSubtitle"]))
# Parse markdown sections
lines = md_content.split("\n")
i = 0
diag_idx = 0
in_code = False
code_text = []
while i < len(lines):
line = lines[i].strip()
if line.startswith("```"):
if line.startswith("```mermaid"):
i += 1
while i < len(lines) and not lines[i].strip().startswith("```"): i += 1
if diag_idx < len(png_diagrams) and png_diagrams[diag_idx]:
story.append(Spacer(1, 0.4*cm))
img_path = png_diagrams[diag_idx]
story.append(KeepTogether([Paragraph(f"Figure {diag_idx + 1}: Video Lab Forensic Processing Workflow (v11.0)", ParagraphStyle("FigCap", parent=styles["Normal"], fontName="Helvetica-Bold", fontSize=8, textColor=DARK, spaceAfter=8, alignment=TA_CENTER)), Image(img_path, width=17*cm, height=9.5*cm), Spacer(1, 0.5*cm)]))
diag_idx += 1
i += 1
continue
elif in_code:
in_code = False
code_content = "\n".join(code_text)
story.append(Paragraph(code_content.replace("\n", "
").replace(" ", " "), styles["CodeBlock"]))
code_text = []
else: in_code = True
i += 1
continue
if in_code:
code_text.append(line)
i += 1
continue
if not line: i += 1; continue
if line.startswith("# "): i += 1; continue
elif line.startswith("## "):
story.append(Spacer(1, 0.3*cm))
header_text = clean_markdown_formatting(line[3:])
story.append(Paragraph(header_text, styles["CustomH1"]))
story.append(HRFlowable(width="100%", color=BORDER, thickness=0.8, spaceAfter=8))
i += 1; continue
elif line.startswith("### "):
header_text = clean_markdown_formatting(line[4:])
story.append(Paragraph(header_text, styles["CustomH2"]))
i += 1; continue
elif line.startswith("#### "):
header_text = clean_markdown_formatting(line[5:])
story.append(Paragraph(header_text, styles["CustomH3"]))
i += 1; continue
if line.startswith("* ") or line.startswith("- "):
list_text = clean_markdown_formatting(line[2:])
story.append(Paragraph(f"• {list_text}", styles["ListItem"]))
i += 1; continue
if re.match(r"^\d+\.\s+", line):
num, text = line.split(".", 1)
story.append(Paragraph(f"{num.strip()}. {clean_markdown_formatting(text.strip())}", styles["ListItem"]))
i += 1; continue
if line.startswith("|"):
table_lines = []
while i < len(lines) and lines[i].strip().startswith("|"):
table_lines.append(lines[i].strip()); i += 1
headers_raw = [h.strip() for h in table_lines[0].split("|")[1:-1]]
headers_p = [Paragraph(f"{clean_markdown_formatting(h)}", styles["TableHeaderText"]) for h in headers_raw]
rows_p = []
for row_line in table_lines[2:]:
cells_raw = [c.strip() for c in row_line.split("|")[1:-1]]
cells_p = [Paragraph(clean_markdown_formatting(c), styles["TableText"]) for c in cells_raw]
rows_p.append(cells_p)
col_count = len(headers_raw)
col_widths = [18.0 / col_count * cm] * col_count
p_table = Table([headers_p] + rows_p, colWidths=col_widths)
p_table.setStyle(TableStyle([("BACKGROUND", (0,0), (-1,0), DARK), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, LIGHT_BG]), ("GRID", (0,0), (-1,-1), 0.5, BORDER), ("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5)]))
story.append(Spacer(1, 0.2*cm)); story.append(p_table); story.append(Spacer(1, 0.3*cm)); continue
story.append(Paragraph(clean_markdown_formatting(line), styles["Body"])); i += 1
def add_footer(canvas, doc):
canvas.saveState()
canvas.setStrokeColor(BORDER); canvas.setLineWidth(0.5); canvas.line(1.5*cm, 1.5*cm, 19.5*cm, 1.5*cm)
canvas.setFont("Helvetica", 7.5); canvas.setFillColor(GRAY)
canvas.drawString(1.5*cm, 1.1*cm, "FakeShield Automated Intelligence Systems — AI Video Lab v5.0.0-PRO")
canvas.drawRightString(19.5*cm, 1.1*cm, f"Page {doc.page}"); canvas.restoreState()
print(f"Building document to {PDF_FILE}...")
doc.build(story, onFirstPage=add_footer, onLaterPages=add_footer)
print("PDF compilation complete!")