Files
recap/build-guide-pdf.py
T
2026-04-09 15:03:31 -05:00

327 lines
12 KiB
Python

from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.colors import HexColor
from reportlab.lib.units import inch
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, KeepTogether, HRFlowable, Preformatted
)
from reportlab.lib import colors
import os
OUTPUT = os.path.join(os.path.dirname(__file__), "GET-STARTED.pdf")
doc = SimpleDocTemplate(
OUTPUT,
pagesize=letter,
topMargin=0.75 * inch,
bottomMargin=0.75 * inch,
leftMargin=0.85 * inch,
rightMargin=0.85 * inch,
)
styles = getSampleStyleSheet()
# Custom styles
styles.add(ParagraphStyle(
"DocTitle", parent=styles["Title"],
fontSize=26, leading=32, textColor=HexColor("#0f172a"),
spaceAfter=4, fontName="Helvetica-Bold",
))
styles.add(ParagraphStyle(
"DocSubtitle", parent=styles["Normal"],
fontSize=13, leading=18, textColor=HexColor("#64748b"),
spaceAfter=24, alignment=TA_CENTER,
))
styles.add(ParagraphStyle(
"H1", parent=styles["Heading1"],
fontSize=20, leading=26, textColor=HexColor("#0f172a"),
spaceBefore=28, spaceAfter=10, fontName="Helvetica-Bold",
))
styles.add(ParagraphStyle(
"H2", parent=styles["Heading2"],
fontSize=15, leading=20, textColor=HexColor("#1e293b"),
spaceBefore=20, spaceAfter=8, fontName="Helvetica-Bold",
))
styles.add(ParagraphStyle(
"Body", parent=styles["Normal"],
fontSize=11, leading=17, textColor=HexColor("#334155"),
spaceAfter=8, fontName="Helvetica",
))
styles.add(ParagraphStyle(
"BodyBold", parent=styles["Normal"],
fontSize=11, leading=17, textColor=HexColor("#1e293b"),
spaceAfter=8, fontName="Helvetica-Bold",
))
styles.add(ParagraphStyle(
"CodeBlock", parent=styles["Code"],
fontSize=10, leading=14, textColor=HexColor("#1e293b"),
backColor=HexColor("#f1f5f9"),
fontName="Courier", leftIndent=16, rightIndent=16,
spaceBefore=4, spaceAfter=8,
borderPadding=(8, 8, 8, 8),
))
styles.add(ParagraphStyle(
"BulletItem", parent=styles["Normal"],
fontSize=11, leading=17, textColor=HexColor("#334155"),
leftIndent=24, bulletIndent=10, spaceAfter=4,
fontName="Helvetica",
))
styles.add(ParagraphStyle(
"StepNum", parent=styles["Normal"],
fontSize=11, leading=17, textColor=HexColor("#334155"),
leftIndent=24, bulletIndent=4, spaceAfter=4,
fontName="Helvetica",
))
styles.add(ParagraphStyle(
"Note", parent=styles["Normal"],
fontSize=10, leading=15, textColor=HexColor("#92400e"),
backColor=HexColor("#fffbeb"),
leftIndent=12, rightIndent=12,
spaceBefore=8, spaceAfter=8,
borderPadding=(8, 8, 8, 8),
fontName="Helvetica",
))
styles.add(ParagraphStyle(
"Link", parent=styles["Normal"],
fontSize=11, leading=17, textColor=HexColor("#2563eb"),
spaceAfter=4, fontName="Helvetica",
))
def code_block(text):
"""Create a code block with background using Preformatted to preserve newlines."""
return Preformatted(text, styles["CodeBlock"])
def hr():
return HRFlowable(width="100%", thickness=1, color=HexColor("#e2e8f0"), spaceBefore=12, spaceAfter=12)
story = []
# -- Title --
story.append(Spacer(1, 40))
story.append(Paragraph("YouTube Transcript Summarizer", styles["DocTitle"]))
story.append(Paragraph("Mac Setup Guide", styles["DocSubtitle"]))
story.append(hr())
# -- What you're setting up --
story.append(Paragraph("What You're Setting Up", styles["H1"]))
story.append(Paragraph(
"This app has two parts that work together:",
styles["Body"]
))
story.append(Paragraph(
"<b>1. A small server</b> that runs locally on your Mac (not on the internet -- only you can access it). "
"It downloads YouTube audio and talks to Google's Gemini AI.",
styles["BulletItem"]
))
story.append(Paragraph(
"<b>2. A webpage</b> that the server hosts in your browser at <font face='Courier'>http://localhost:3001</font>.",
styles["BulletItem"]
))
story.append(Spacer(1, 4))
story.append(Paragraph(
"Nothing is uploaded anywhere. Everything runs on your machine.",
styles["BodyBold"]
))
# -- Where to save --
story.append(Paragraph("Where to Save the Project", styles["H1"]))
story.append(Paragraph(
"The <font face='Courier' color='#1e293b'>youtube-summarizer</font> folder you downloaded can live anywhere, "
"but the standard Mac convention for projects like this is:",
styles["Body"]
))
story.append(code_block("~/Projects/youtube-summarizer"))
story.append(Paragraph(
"The <font face='Courier'>~</font> means your home folder (e.g., <font face='Courier'>/Users/grant</font>). "
"If you don't have a <font face='Courier'>Projects</font> folder yet, you'll create one below.",
styles["Body"]
))
story.append(Paragraph("To move the files there:", styles["BodyBold"]))
story.append(Paragraph("1. Open <b>Finder</b>", styles["StepNum"]))
story.append(Paragraph("2. Go to your home folder (press <b>Cmd + Shift + H</b>)", styles["StepNum"]))
story.append(Paragraph("3. Create a new folder called <b>Projects</b> if you don't have one", styles["StepNum"]))
story.append(Paragraph("4. Drag the <font face='Courier'>youtube-summarizer</font> folder from your Cowork downloads into <b>Projects</b>", styles["StepNum"]))
story.append(Spacer(1, 8))
story.append(Paragraph("You should end up with this structure:", styles["Body"]))
story.append(code_block(
"~/Projects/youtube-summarizer/\n"
"|-- public/\n"
"| +-- index.html\n"
"|-- server/\n"
"| |-- index.js\n"
"| +-- package.json\n"
"|-- setup.sh\n"
"|-- Start Summarizer.command <-- double-click to launch!\n"
"+-- GET-STARTED.pdf <-- this file"
))
# -- Step 1 --
story.append(PageBreak())
story.append(Paragraph("Step 1: Install Node.js and yt-dlp", styles["H1"]))
story.append(Paragraph(
"You need two things installed: Node.js (runs the server) and yt-dlp (downloads YouTube audio). "
"The easiest way to install both is with Homebrew.",
styles["Body"]
))
story.append(Paragraph("If you don't have Homebrew yet:", styles["BodyBold"]))
story.append(Paragraph("1. Open <b>Terminal</b> (press <b>Cmd + Space</b>, type \"Terminal\", hit Enter)", styles["StepNum"]))
story.append(Paragraph("2. Paste this and press Enter:", styles["StepNum"]))
story.append(code_block('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'))
story.append(Paragraph("3. Follow the prompts (it may ask for your Mac password)", styles["StepNum"]))
story.append(Spacer(1, 8))
story.append(Paragraph("Then install Node.js and yt-dlp:", styles["BodyBold"]))
story.append(code_block("brew install node yt-dlp"))
story.append(Spacer(1, 4))
story.append(Paragraph("Verify they're installed:", styles["Body"]))
story.append(code_block("node --version\nyt-dlp --version"))
story.append(Paragraph(
"You should see version numbers for both. If so, you're ready for the next step.",
styles["Body"]
))
# -- Step 2 --
story.append(Paragraph("Step 2: Install Server Dependencies", styles["H1"]))
story.append(Paragraph(
"This is a one-time step. In Terminal, run:",
styles["Body"]
))
story.append(code_block("cd ~/Projects/youtube-summarizer/server\nnpm install"))
story.append(Paragraph(
"This downloads the JavaScript libraries the server needs. You'll see some output scroll by -- "
"when it finishes and shows you a new prompt, you're done.",
styles["Body"]
))
# -- Step 3 --
story.append(Paragraph("Step 3: Add Your Gemini API Key", styles["H1"]))
story.append(Paragraph("The app needs a Google Gemini API key to transcribe and analyze videos.", styles["Body"]))
story.append(Spacer(1, 4))
story.append(Paragraph("Get a free key:", styles["BodyBold"]))
story.append(Paragraph("1. Go to <b><font color='#2563eb'>https://aistudio.google.com/apikey</font></b>", styles["StepNum"]))
story.append(Paragraph("2. Sign in with your Google account", styles["StepNum"]))
story.append(Paragraph("3. Click \"Create API Key\"", styles["StepNum"]))
story.append(Paragraph("4. Copy the key -- you'll paste it in the app after launching", styles["StepNum"]))
# -- Step 4 --
story.append(PageBreak())
story.append(Paragraph("Step 4: Launch the App", styles["H1"]))
story.append(Paragraph(
"This is the fun part. You have two ways to launch:",
styles["Body"]
))
story.append(Spacer(1, 4))
story.append(Paragraph("Option A: Double-click (recommended)", styles["H2"]))
story.append(Paragraph(
"Find <font face='Courier'>Start Summarizer.command</font> in your project folder and double-click it.",
styles["Body"]
))
story.append(Paragraph(
"The first time, macOS may say the file can't be opened because it's from an unidentified developer. "
"If this happens: right-click the file, choose <b>Open</b>, then click <b>Open</b> in the dialog. "
"You only need to do this once.",
styles["Note"]
))
story.append(Paragraph(
"A Terminal window will open, the server will start, and your browser will automatically open to the app. "
"That's it!",
styles["Body"]
))
story.append(Spacer(1, 8))
story.append(Paragraph("Option B: Terminal", styles["H2"]))
story.append(Paragraph("If you prefer the command line:", styles["Body"]))
story.append(code_block("cd ~/Projects/youtube-summarizer/server\nnpm start"))
story.append(Paragraph(
"Then open <b><font color='#2563eb'>http://localhost:3001</font></b> in your browser.",
styles["Body"]
))
# -- Step 5 --
story.append(Spacer(1, 8))
story.append(Paragraph("Step 5: Summarize a Video", styles["H1"]))
story.append(Paragraph("1. In the app, click <b>\"Gemini API Settings\"</b> and paste your API key", styles["StepNum"]))
story.append(Paragraph("2. Paste a YouTube URL into the main input", styles["StepNum"]))
story.append(Paragraph("3. Click <b>Summarize</b>", styles["StepNum"]))
story.append(Paragraph("4. Watch the 3-step pipeline: Download audio -> Transcribe -> Analyze topics", styles["StepNum"]))
story.append(Paragraph("5. Click any topic section to expand the full transcript with clickable timestamps", styles["StepNum"]))
story.append(Spacer(1, 4))
story.append(Paragraph(
"Your API key is saved in your browser so you don't have to re-enter it each time.",
styles["Body"]
))
# -- Day-to-day --
story.append(hr())
story.append(Paragraph("Day-to-Day Usage", styles["H1"]))
story.append(Paragraph(
"After the one-time setup above, here's all you need to do each time:",
styles["Body"]
))
story.append(Paragraph(
"1. Double-click <font face='Courier'>Start Summarizer.command</font>",
styles["StepNum"]
))
story.append(Paragraph(
"2. Your browser opens automatically -- paste a YouTube link and go",
styles["StepNum"]
))
story.append(Paragraph(
"3. When done, close the Terminal window that opened (this stops the server)",
styles["StepNum"]
))
story.append(Spacer(1, 4))
story.append(Paragraph("That's it. One double-click.", styles["BodyBold"]))
# -- Troubleshooting --
story.append(PageBreak())
story.append(Paragraph("Troubleshooting", styles["H1"]))
troubles = [
(
"macOS won't open Start Summarizer.command",
"Right-click the file, choose <b>Open</b>, then click <b>Open</b> in the security dialog. "
"This only happens the first time."
),
(
"\"Cannot connect to backend at localhost:3001\"",
"The server isn't running. Double-click <font face='Courier'>Start Summarizer.command</font> again."
),
(
"\"yt-dlp not installed\"",
"Run <font face='Courier'>brew install yt-dlp</font> in Terminal, then relaunch the app."
),
(
"Download fails or hangs",
"yt-dlp might be outdated. Click the \"Update now\" button in the app, or run "
"<font face='Courier'>brew upgrade yt-dlp</font> in Terminal."
),
(
"\"Gemini API error: 403\" or \"401\"",
"Your API key is invalid or expired. Get a new one from "
"<font color='#2563eb'>https://aistudio.google.com/apikey</font>"
),
(
"Long videos take a while",
"Normal. A 1-hour video takes ~30 seconds to download, 30-60 seconds for Gemini "
"to transcribe, then 10-20 seconds for topic analysis. The app shows live progress."
),
]
for problem, solution in troubles:
story.append(Paragraph(f"<b>{problem}</b>", styles["BodyBold"]))
story.append(Paragraph(solution, styles["BulletItem"]))
story.append(Spacer(1, 6))
# Build
doc.build(story)
print(f"PDF created: {OUTPUT}")