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( "1. A small server 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( "2. A webpage that the server hosts in your browser at http://localhost:3001.", 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 youtube-summarizer 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 ~ means your home folder (e.g., /Users/grant). " "If you don't have a Projects folder yet, you'll create one below.", styles["Body"] )) story.append(Paragraph("To move the files there:", styles["BodyBold"])) story.append(Paragraph("1. Open Finder", styles["StepNum"])) story.append(Paragraph("2. Go to your home folder (press Cmd + Shift + H)", styles["StepNum"])) story.append(Paragraph("3. Create a new folder called Projects if you don't have one", styles["StepNum"])) story.append(Paragraph("4. Drag the youtube-summarizer folder from your Cowork downloads into Projects", 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 Terminal (press Cmd + Space, 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 https://aistudio.google.com/apikey", 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 Start Summarizer.command 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 Open, then click Open 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 http://localhost:3001 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 \"Gemini API Settings\" 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 Summarize", 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 Start Summarizer.command", 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 Open, then click Open in the security dialog. " "This only happens the first time." ), ( "\"Cannot connect to backend at localhost:3001\"", "The server isn't running. Double-click Start Summarizer.command again." ), ( "\"yt-dlp not installed\"", "Run brew install yt-dlp 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 " "brew upgrade yt-dlp in Terminal." ), ( "\"Gemini API error: 403\" or \"401\"", "Your API key is invalid or expired. Get a new one from " "https://aistudio.google.com/apikey" ), ( "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"{problem}", styles["BodyBold"])) story.append(Paragraph(solution, styles["BulletItem"])) story.append(Spacer(1, 6)) # Build doc.build(story) print(f"PDF created: {OUTPUT}")