YouTube Video Analyzer with AI
Analyze YouTube videos with Gemini AI and output reports to Google Docs.
Introduction
30 Second Summary
You watch hours of video content every week. Extracting structured notes from those videos takes just as long as watching them in the first place.
In this project, you will build a local web app that analyzes any public YouTube video using Google's Gemini AI and automatically creates a formatted Google Doc report with timestamped speech transcriptions and visual breakdowns. The entire app is built by pasting 3 carefully crafted prompts into GitHub Copilot Chat.
What You'll Build
Paste a YouTube URL into your app, click one button, and find a professionally formatted report waiting in your Google Drive with every segment of the video broken down by what was said, how it was said, and what was shown on screen.
By the end of this project, you'll have:
- A video analysis tool where you paste any public YouTube URL and receive a detailed, timestamped analysis document in your Google Drive.
- A working Google OAuth2 sign-in flow that lets your app create formatted documents directly in your Drive with proper headings and structure.
- An AI analysis prompt that extracts speech transcription, tone characteristics, visual descriptions, and background audio from video content simultaneously.
- Secret Mission: Add a polished HTML frontend with a loading spinner and video title display.
Are there any prerequisites?
You need Node.js v18+ installed, VS Code with the GitHub Copilot Chat extension, and a Google account. Step 1 walks you through setting up the Google Cloud credentials from scratch.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. This step is your moment to commit to the goal before any hands-on work begins.
Set Up Google Cloud Project and API Keys
The video analyzer needs to talk to two different Google services. Gemini AI handles the video analysis, and the Google Docs API creates formatted reports in your Drive.
Each service requires its own credential. In this step, you'll create a Gemini API key for the AI analysis and OAuth 2.0 credentials that let the app create documents on your behalf.
In this step, get ready to:
- Get a Gemini API key from Google AI Studio.
- Create a Google Cloud project and enable the Google Docs API.
- Configure OAuth 2.0 credentials for your app.
Get your Gemini API key
Google AI Studio is the fastest way to get a Gemini API key. The key lets your server send YouTube videos to Gemini for analysis.
- Open your browser and navigate to aistudio.google.com/apikey.
- Sign in with your Google account if prompted.
- Click Create API key.
- Select an existing Google Cloud project, or let AI Studio create a new one for you.
- Copy the generated API key and paste it somewhere safe (you'll need it shortly).
Why is this key safe to create?
Keys generated in AI Studio are automatically restricted to the Gemini API. This means even if the key were exposed, it could only be used for Gemini requests.
Seeing a permission error?
If AI Studio won't create a key, try opening the page in an incognito window and signing in again. Make sure your Google account is in a supported region.
Help me fix a permission error when creating a Gemini API key in Google AI Studio
Create a Google Cloud project and enable the Docs API
Your Gemini key is ready, but the app also needs permission to create Google Docs on the user's behalf. That requires a Google Cloud project with the Docs API turned on.
- Open the Google Cloud Console at console.cloud.google.com.
- Click Select a project at the top of the page.
- Click New Project.
- Enter a project name (e.g. Video Analyzer) and click Create.
- Wait a few seconds for the project to be created, then select it from the project dropdown.
Now enable the Google Docs API for this project.
- In the left sidebar, click APIs & Services.
- Click Library.
- Search for Google Docs API in the search bar.
- Click on Google Docs API in the results.
- Click Enable.
The page now shows the Google Docs API overview with a status of enabled.
Configure the OAuth consent screen and create credentials
Before Google lets your app request access to a user's Drive, you need to configure a consent screen. This is the page users see when they click "Sign in with Google" in your app.
- In the left sidebar, navigate to Google Auth platform.
- Click Branding.
- If you see a message saying the platform is not configured yet, click Get Started.
- Under App Information, enter an app name (e.g. Video Analyzer).
- Select your email address as the User support email.
- Click Next.
- Under Audience, select External.
- Click Next.
- Enter your email address as the contact email.
- Click Next.
- Review the summary and click Create to finish.
Now add yourself as a test user so you can sign in during development.
- Navigate to Google Auth platform > Audience.
- Under Test users, click Add users.
- Enter your own Google email address and save.
Why External and testing mode?
Selecting External means anyone with a Google account can eventually use your app. Testing mode limits access to the specific test users you add (up to 100).
This avoids a full Google verification process while still letting you develop and test locally.
With the consent screen ready, you can now create the OAuth Client ID that your Express server will use to initiate the sign-in flow.
- Navigate to Google Auth platform > Clients.
- Click Create Client.
- For Application type, select Web application.
- In the Name field, enter Video Analyzer.
- Under Authorized JavaScript origins, click Add URI and enter:
http://localhost:3000
- Under Authorized redirect URIs, click Add URI and enter:
http://localhost:3000/oauth2callback
- Click Create.
A dialog appears showing your Client ID and Client Secret. Copy both values and paste them somewhere safe alongside your Gemini API key.
Why two different authentication methods?
The Gemini API key is a simple server-to-server credential. It identifies your app but doesn't involve any user.
OAuth 2.0 is different. It lets a user grant your app permission to act on their behalf (creating Docs in their Drive). The Client ID and Secret prove your app's identity during this handshake.
Don't see the Client Secret?
The secret is shown once in the creation dialog. If you missed it, click on your client name in the Clients list to open its details page where you can find the secret.
Help me find my OAuth Client Secret in Google Cloud Console
Let's confirm everything is in place. You should now have three values saved:
- Your Gemini API key (from AI Studio).
- Your OAuth Client ID (from Cloud Console).
- Your OAuth Client Secret (from Cloud Console).
To verify: open aistudio.google.com/apikey and confirm your key appears in the list. Then in the Cloud Console, navigate to the Google Docs API overview page and confirm it shows enabled.
Your credentials are locked and loaded. Next up, you'll use GitHub Copilot Chat to generate a full Express server that puts these credentials to work with a working Google sign-in flow.
Generate the Express Server with OAuth2 Login
You now have the credentials your app needs. The next challenge is giving the app a way for users to grant permission to create Google Docs on their behalf.
In this step, you will generate a complete Express server using a single prompt in GitHub Copilot Chat. The server handles the full OAuth 2.0 sign-in flow so users can authorize your app to write to their Google Drive.
In this step, get ready to:
- Create a project folder with a Node.js environment and credentials file.
- Generate an Express server with OAuth2 routes using GitHub Copilot Chat.
- Install dependencies and verify sign-in works end-to-end.
Create the project folder and credentials file
Every Node.js project starts with a folder and a package.json. You also need a .env file to store your API credentials safely outside of code.
- Open your terminal in VS Code by pressing Ctrl+` (macOS and Windows).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir video-analyzer
cd video-analyzer
- Initialize a Node.js project by running:
npm init -y
You should see a package.json printed in the terminal confirming the file was created.
Now you need to tell Node.js to use ES module syntax (import instead of require).
- Open package.json in VS Code by clicking on it in the Explorer sidebar.
- Add "type": "module" on a new line after "version": "1.0.0", so the top of your file looks like this:
{
"name": "video-analyzer",
"version": "1.0.0",
"type": "module"
}
What does type: module do?
By default, Node.js uses the older CommonJS module system (require()). Setting "type": "module" switches your project to ES modules, which use the import syntax that modern JavaScript libraries expect.
- Save package.json.
- Create a .env file in your project folder by running:
touch .env
- Open .env in VS Code and paste the following, replacing each placeholder with the real values you copied in Step 1:
GEMINI_API_KEY=your-gemini-api-key-here
GOOGLE_CLIENT_ID=your-oauth-client-id-here
GOOGLE_CLIENT_SECRET=your-oauth-client-secret-here
Why a .env file?
Storing credentials in a .env file keeps them out of your source code. The dotenv package loads these values into process.env at runtime so your code can access them without hardcoding secrets.
- Save .env.
- Confirm both files exist by running:
ls -a
You should see .env and package.json listed in the output.
Don't see .env in the list?
The -a flag shows hidden files (files starting with a dot). If you ran ls without -a, dotfiles like .env are hidden. Run ls -a to see them.
Help me see my .env file in the terminal
Generate the Express server with Copilot
Instead of writing the server code by hand, you will paste a detailed prompt into GitHub Copilot Chat. The prompt tells Copilot exactly which packages, routes, and patterns to use.
- Open the Chat view in VS Code by pressing Cmd+Ctrl+I (macOS) or Ctrl+Alt+I (Windows).
- Copy the following prompt and paste it into the Chat input, then press Enter:
Generate a Node.js Express server in a file called server.js with the following requirements:
1. Use ES module imports (import/export syntax)
2. Import and configure dotenv using "import 'dotenv/config'"
3. Use express, express-session, googleapis, and @google/genai packages
4. Set up express.json() middleware, express.static("public") for serving static files, and express-session with secret "video-analyzer-session"
5. Create a Google OAuth2 client using google.auth.OAuth2 with GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET from process.env, and redirect URI "http://localhost:3000/oauth2callback"
6. Initialize GoogleGenAI with the GEMINI_API_KEY from process.env: new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY })
7. Create these routes:
- GET /auth: generates an auth URL with access_type "offline" and scope "https://www.googleapis.com/auth/documents", then redirects to it
- GET /oauth2callback: exchanges the code query param for tokens using oauth2Client.getToken(code), stores tokens in req.session.tokens, redirects to "/"
- GET /auth-status: returns JSON { authenticated: !!req.session.tokens }
8. Listen on port 3000 and log "Video Analyzer running at http://localhost:3000"
Also generate a public/index.html file that:
- Has a sign-in button that links to /auth
- On page load, fetches /auth-status and if authenticated, hides the sign-in section and shows a section with text "Paste a public YouTube URL to analyze:" with an input field and an Analyze button
- Uses simple inline CSS styling
What does this prompt do?
This prompt instructs Copilot to build two files. The server.js file handles the OAuth2 dance (redirect to Google, exchange the authorization code for tokens, remember the user's session). The public/index.html file gives you a simple page to trigger the sign-in and confirm it worked.
Being specific about package names, method signatures, and route paths steers Copilot toward generating code that works on the first try.
Copilot will respond with generated code for both files. Now you need to create them in your project.
- Create a server.js file in your video-analyzer folder.
- Copy Copilot's generated server code and paste it into server.js.
- Create a public folder inside video-analyzer, then create an index.html file inside it.
- Copy Copilot's generated HTML code and paste it into public/index.html.
- Save both files.
Copilot's output may vary slightly
Copilot generates code based on your prompt, so the exact formatting or variable names might differ from the reference code below. That is fine as long as the routes, package imports, and session handling match the structure. You can compare against the reference in the next section.
✔️ Awesome, I've got everything!
Great. Double check that you saved both server.js and public/index.html. Then move on to installing dependencies below.
ⓧ I'd like to double check the full code
Compare your server.js against this reference. The top half handles imports, middleware, and client setup:
import "dotenv/config";
import express from "express";
import session from "express-session";
import { google } from "googleapis";
import { GoogleGenAI } from "@google/genai";
const app = express();
const port = 3000;
app.use(express.json());
app.use(express.static("public"));
app.use(
session({
secret: "video-analyzer-session",
resave: false,
saveUninitialized: false,
})
);
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
"http://localhost:3000/oauth2callback"
);
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
The bottom half defines the routes and starts the server:
app.get("/auth", (req, res) => {
const url = oauth2Client.generateAuthUrl({
access_type: "offline",
scope: ["https://www.googleapis.com/auth/documents"],
});
res.redirect(url);
});
app.get("/oauth2callback", async (req, res) => {
const { code } = req.query;
const { tokens } = await oauth2Client.getToken(code);
req.session.tokens = tokens;
res.redirect("/");
});
app.get("/auth-status", (req, res) => {
res.json({ authenticated: !!req.session.tokens });
});
app.listen(port, () => {
console.log(`Video Analyzer running at http://localhost:${port}`);
});
And here is the reference .env file (with your real values substituted for the placeholders):
GEMINI_API_KEY=your-gemini-api-key-here
GOOGLE_CLIENT_ID=your-oauth-client-id-here
GOOGLE_CLIENT_SECRET=your-oauth-client-secret-here
Install dependencies and test sign-in
Your files are ready. Now you need to install the npm packages the server imports, then run it and prove the OAuth2 flow works.
- Install the required packages by running:
npm install googleapis @google/genai express express-session dotenv
You should see npm download and install the packages. A node_modules folder and a package-lock.json file appear in your project.
Seeing permission errors during install?
Make sure your terminal is inside the video-analyzer folder where package.json lives. Run pwd to check. If you are in the wrong directory, run cd ~/Desktop/video-analyzer and try again.
Help me fix npm install errors
Before you run the server, what do you think will happen when you visit http://localhost:3000 in your browser?
- Start the server by running:
node server.js
You should see this message in your terminal:
Video Analyzer running at http://localhost:3000
- Open your browser and navigate to http://localhost:3000.
- Click the Sign in with Google button.
What happens during OAuth sign-in?
Clicking sign-in redirects you to Google's consent screen. Since your app is in testing mode, Google warns that the app is unverified. Click Continue to proceed past the warning.
After you grant permission, Google sends an authorization code back to your /oauth2callback route. The server exchanges this code for access tokens and stores them in your session.
- On Google's consent screen, click Continue to proceed past the unverified app warning.
- Select your Google account and grant the Google Docs permission.
After granting permission, you are redirected back to http://localhost:3000. The page now shows the YouTube URL input field and an Analyze button, confirming your sign-in was successful.
Still seeing the sign-in button after redirect?
Check that your .env file has the correct Client ID and Client Secret (no extra spaces or quotes around the values). Also verify that the redirect URI in your Google Cloud credentials is exactly http://localhost:3000/oauth2callback with no trailing slash.
If you changed .env, stop the server with Ctrl+C and restart it with node server.js. Environment variables are only loaded at startup.
Help me debug my OAuth sign-in flow
Your OAuth2 flow is working end-to-end. Next up, you will add the route that sends YouTube videos to Gemini for AI analysis.
Add Gemini Video Analysis
Your server is running and the OAuth2 sign-in flow works end-to-end. The app can now act on behalf of the signed-in user.
Time to add the real feature: sending a YouTube video to Gemini for multimodal analysis. Gemini can analyze both the audio and visual streams of a video simultaneously, extracting speech, tone, visuals, and background audio into structured data.
In this step, get ready to:
- Paste Prompt 2 into GitHub Copilot Chat to generate the video analysis route.
- Test the analysis by sending a YouTube URL and viewing the structured JSON output in the terminal.
Paste the analysis prompt into GitHub Copilot Chat
You need a new route that accepts a YouTube URL from the browser, sends it to Gemini for multimodal analysis, and returns structured data. The prompt you give Copilot specifies the exact API pattern, the model, and the JSON structure you expect back.
- Open GitHub Copilot Chat by pressing Ctrl+Cmd+I (macOS) or Ctrl+Alt+I (Windows).
- Copy and paste the following prompt into the chat input, then press Enter:
In my server.js file, add a POST /analyze route that:
1. Checks if req.session.tokens exists, returns 401 JSON error if not
2. Reads youtubeUrl from req.body
3. Validates youtubeUrl contains "youtube.com/watch", returns 400 if invalid
4. Sets oauth2Client credentials from req.session.tokens
5. Calls the Gemini API using the existing `ai` instance with model "gemini-2.5-flash"
6. Passes contents as an array: first item is { fileData: { fileUri: youtubeUrl } }, second is { text: prompt }
7. The prompt variable asks Gemini to analyze the video in 30-second segments, returning JSON with "title" and "segments" array where each segment has: timestamp, track1_caption1 (transcription, 2-3 sentences), track1_caption2 (speech characteristics), track2_caption1 (visuals), track2_caption2 (audio)
8. Strips markdown code fences from the response text, parses JSON, logs it with console.log
9. Returns { analysis } as the response
10. Wraps in try/catch, returns 500 with error message
Use ai.models.generateContent({ model: "gemini-2.5-flash", contents }) for the API call.
Also add a simple HTML form to public/index.html with a YouTube URL input and Analyze button that POSTs to /analyze when the user is signed in.
Why does this prompt work well with Copilot?
Detailed, specific prompts produce better results than vague instructions. This prompt names exact packages, methods, route structure, and the expected JSON shape.
Copilot uses your open server.js file as context, so it knows about the existing ai instance and oauth2Client variable already in your code.
- Review the code Copilot generates in the chat response.
- Click Apply (or the equivalent accept button) to insert the generated code into your project files.
Verify the generated analyze route
Copilot may produce slightly different code than what this project expects. Check that the key pieces are present and correct in your server.js.
- Open server.js and confirm the /analyze route exists above the app.listen call.
- Verify the route includes authentication and URL validation that matches this pattern:
app.post("/analyze", async (req, res) => {
if (!req.session.tokens) {
return res.status(401).json({ error: "Not authenticated" });
}
const { youtubeUrl } = req.body;
if (!youtubeUrl || !youtubeUrl.includes("youtube.com/watch")) {
return res.status(400).json({ error: "Invalid YouTube URL" });
}
try {
oauth2Client.setCredentials(req.session.tokens);
What do these checks do?
- The tokens check ensures the user has signed in via Google before analyzing anything.
- The URL validation confirms the input is a YouTube watch page, not an arbitrary link.
- setCredentials loads the user's OAuth tokens so the app can later create Google Docs on their behalf.
- Next, confirm the Gemini API call uses the correct pattern with the YouTube URL passed as fileData:
const contents = [
{ fileData: { fileUri: youtubeUrl } },
{ text: prompt },
];
const response = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: contents,
});
const responseText = response.text.replace(/```json\n?|```\n?/g, "").trim();
const analysis = JSON.parse(responseText);
console.log(JSON.stringify(analysis, null, 2));
res.json({ analysis });
How does Gemini receive a YouTube video?
The fileData object passes the YouTube URL directly to Gemini as a media reference. Gemini downloads and processes both the audio and video streams on its own servers.
The regex replace strips markdown code fences that Gemini sometimes wraps around JSON responses. After stripping, JSON.parse converts the text into a usable JavaScript object.
What does the Gemini analysis prompt request?
The prompt variable contains detailed instructions asking Gemini to break the video into 30-second segments. Each segment includes five fields:
- timestamp in MM:SS - MM:SS format.
- track1_caption1: speech transcription (2-3 sentences).
- track1_caption2: speech characteristics (tone, pace, volume, clarity).
- track2_caption1: visual description (camera angles, graphics, text overlays).
- track2_caption2: audio description (background music, ambient sounds, effects).
The prompt also asks Gemini to return only valid JSON with a title field and segments array. This structure is what you will format into a Google Doc in the next step.
- If any of these sections are missing or look different in your generated code, replace the /analyze route with the reference below.
✔️ Awesome, my code looks right!
Save server.js.
ⓧ I'd like to double check the full code
Your server.js should contain all of the following sections in order. Scroll through your file and confirm each is present:
- Imports (dotenv/config, express, express-session, googleapis, @google/genai).
- Express app setup with express.json(), express.static("public"), and session middleware.
- oauth2Client and ai instance creation.
- GET routes: /auth, /oauth2callback, and /auth-status (from Step 2).
- POST /analyze route with auth check, URL validation, Gemini API call, JSON parsing, and console.log.
- app.listen on port 3000.
If the /analyze route is missing or substantially different, paste the prompt into Copilot Chat again and specify that it should match the pattern shown in the reference snippets above.
Test the video analysis
With the route in place, you can send a real YouTube video to Gemini and watch the analysis come back as structured data.
- Stop your running server with Ctrl+C in the terminal.
- Restart it by running:
node server.js
- Switch to your browser at http://localhost:3000.
- Sign in with Google again (your session was lost when the server restarted).
- Paste a short public YouTube video URL into the input field.
Pick a short video
Choose a video under 2 minutes for your first test. Longer videos take more time to analyze and use more of your free tier quota (8 hours of YouTube video per day).
Before you click Analyze: what do you expect to see? Will the browser show the full analysis, or will it appear somewhere else?
- Click Analyze and wait 30-60 seconds for Gemini to process the video.
- Switch to your terminal while you wait.
You should see structured JSON printed in the terminal, showing timestamped segments with all five fields populated. The browser also receives a JSON response, but it is raw unformatted data.
What should I see?
Your terminal output should look something like this (content will vary by video):
{
"title": "Video Title Here",
"segments": [
{
"timestamp": "00:00 - 00:30",
"track1_caption1": "Transcription of speech...",
"track1_caption2": "Tone and pace description...",
"track2_caption1": "Visual elements described...",
"track2_caption2": "Audio elements described..."
}
]
}
Each segment covers up to 30 seconds of the video. A 2-minute video produces roughly 4 segments.
Not seeing any output?
- If you see a 401 error, sign in again at http://localhost:3000 before clicking Analyze.
- If you see a 400 error, make sure your URL is a full YouTube watch URL like https://www.youtube.com/watch?v=... (not a short youtu.be link).
- If you see "Analysis failed" with a 500 error, check that your GEMINI_API_KEY in .env is valid and that the video is public (not private or unlisted).
- If JSON.parse fails, Gemini may have returned non-JSON text. Try again with a different video.
Help me debug my Gemini video analysis route
The analysis data is flowing, but it is raw JSON in a terminal window. Nobody wants to read a JSON blob. Next up, you will turn this structured data into a professionally formatted Google Doc that you can share with anyone.
Create the Formatted Google Doc
Your /analyze endpoint now returns structured JSON with timestamps, transcription, and visual data. That output is powerful, but it lives in your terminal. Nobody else can read it.
In this step, you will transform the raw analysis into a professionally formatted Google Doc with headings, track labels, and timestamped segments. The result is a shareable document that appears in your Google Drive with one click.
In this step, get ready to:
- Paste Prompt 3 into GitHub Copilot Chat to generate the Google Docs creation code and a basic frontend.
- Test the complete end-to-end flow from sign-in to Google Doc output.
Paste Prompt 3 into GitHub Copilot Chat
This final prompt asks Copilot to do two things. First, it modifies the /analyze route so that after Gemini returns the analysis JSON, it creates a Google Doc and writes the two-track structure into it using headings. Second, it creates a basic HTML frontend so you can interact with the app in the browser.
- Open GitHub Copilot Chat in VS Code and paste the following prompt:
Modify server.js and create public/index.html for my video analyzer app.
For server.js:
- In the POST /analyze route, after parsing the Gemini JSON response, remove the console.log and res.json({ analysis }) lines.
- Add code to create a new Google Doc using the googleapis package: google.docs({ version: "v1", auth: oauth2Client }), then docs.documents.create with title "Video Analysis: ${analysis.title}".
- After creating the doc, call docs.documents.batchUpdate with insertText and updateParagraphStyle requests to format the content. Use a buildDocRequests helper function.
- The buildDocRequests function loops through analysis.segments. For each segment: insert the timestamp as HEADING_3, then "Track 1: Speech" as HEADING_1, "Speech Transcription" as HEADING_2 with the content as NORMAL_TEXT, "Speech Characteristics" as HEADING_2 with content, then "Track 2: Visual + Audio" as HEADING_1, "Visuals" as HEADING_2 with content, "Audio" as HEADING_2 with content.
- Text insertion starts at index 1 and tracks position. Use updateParagraphStyle with namedStyleType and fields: "namedStyleType".
- Return JSON with docUrl (https://docs.google.com/document/d/${documentId}/edit) and title.
For public/index.html:
- Simple page with system-ui font, max-width 600px centered.
- An auth-section with "Sign in with Google" button linking to /auth.
- A hidden analyze-section with URL input, Analyze button, and result div.
- JavaScript: checkAuth() fetches /auth-status and shows the analyze section if authenticated.
- analyze() function: POST to /analyze with the YouTube URL, show the doc link on success or error message on failure. Disable button and show "Analyzing..." while processing.
What does this prompt do?
This prompt instructs Copilot to replace the temporary terminal output with real Google Docs API calls. The documents.create method makes a new doc, then batchUpdate inserts all the text and applies heading styles in a single API call.
The frontend gives you a browser-based interface instead of having to use curl in the terminal. It checks sign-in status and sends the YouTube URL to your server.
- Review Copilot's output and apply it to your project files.
- In server.js, confirm the /analyze route now contains the Google Docs creation code. Find this section after const analysis = JSON.parse(responseText); and verify it matches:
const docs = google.docs({ version: "v1", auth: oauth2Client });
const doc = await docs.documents.create({
requestBody: { title: `Video Analysis: ${analysis.title}` },
});
const documentId = doc.data.documentId;
const requests = buildDocRequests(analysis);
await docs.documents.batchUpdate({
documentId: documentId,
requestBody: { requests: requests },
});
res.json({
docUrl: `https://docs.google.com/document/d/${documentId}/edit`,
title: analysis.title,
});
What does this code do?
- google.docs({ version: "v1", auth: oauth2Client }) creates an authenticated Docs API client using the user's OAuth tokens.
- documents.create makes a brand new Google Doc in the user's Drive with the video title in its name.
- documents.batchUpdate sends all the text insertions and heading styles in one efficient API call.
- The response gives the browser a direct edit link to the new doc.
- Verify the buildDocRequests helper function exists below the /analyze route. Confirm it starts with these helper definitions:
function buildDocRequests(analysis) {
const requests = [];
let index = 1;
const insertText = (text) => {
requests.push({ insertText: { location: { index }, text } });
const start = index;
index += text.length;
return { start, end: index };
};
const applyStyle = (start, end, style) => {
requests.push({
updateParagraphStyle: {
range: { startIndex: start, endIndex: end },
paragraphStyle: { namedStyleType: style },
fields: "namedStyleType",
},
});
};
Why start at index 1?
Every Google Doc begins with a section break at index 0. Text insertion must start at index 1 or later. The insertText helper tracks the current position so each piece of content lands in the right spot.
The applyStyle helper applies a named heading style (like HEADING_1 or HEADING_2) to a range of text that was just inserted.
- Confirm the function continues with the loop that builds the two-track structure for each segment:
for (const segment of analysis.segments) {
const tsRange = insertText(`${segment.timestamp}\n`);
applyStyle(tsRange.start, tsRange.end, "HEADING_3");
const t1Range = insertText("Track 1: Speech\n");
applyStyle(t1Range.start, t1Range.end, "HEADING_1");
const c1Label = insertText("Speech Transcription\n");
applyStyle(c1Label.start, c1Label.end, "HEADING_2");
insertText(`${segment.track1_caption1}\n\n`);
const c2Label = insertText("Speech Characteristics\n");
applyStyle(c2Label.start, c2Label.end, "HEADING_2");
insertText(`${segment.track1_caption2}\n\n`);
const t2Range = insertText("Track 2: Visual + Audio\n");
applyStyle(t2Range.start, t2Range.end, "HEADING_1");
const v1Label = insertText("Visuals\n");
applyStyle(v1Label.start, v1Label.end, "HEADING_2");
insertText(`${segment.track2_caption1}\n\n`);
const a1Label = insertText("Audio\n");
applyStyle(a1Label.start, a1Label.end, "HEADING_2");
insertText(`${segment.track2_caption2}\n\n`);
}
return requests;
}
How does the doc get structured?
For every 30-second segment of the video, the loop inserts the timestamp as a HEADING_3, then creates two tracks. Track 1 covers speech (transcription and characteristics). Track 2 covers visuals and audio.
Each track label uses HEADING_1 and each caption label uses HEADING_2. The actual content is plain NORMAL_TEXT (the default when no style is applied).
- Confirm the public/index.html file was created. Check that it contains an auth-section div with a sign-in button and an analyze-section div with an input, button, and result area.
- Save both server.js and public/index.html.
Copilot output looks different?
Copilot may generate slightly different variable names or formatting. The key things to check: the route calls docs.documents.create then docs.documents.batchUpdate, and returns docUrl and title in the response JSON.
If the structure differs significantly, replace the generated code with the verified blocks shown above.
help me fix my Copilot-generated Google Docs code to match the expected structure
Test the complete end-to-end flow
Everything is connected now. The server analyzes the video, creates a formatted Google Doc, and the frontend lets you trigger it all from the browser.
- Stop any running server with Ctrl+C, then restart it by running:
node server.js
- Open http://localhost:3000 in your browser.
- Click Sign in with Google and complete the consent flow.
After signing in, you should see the analyze section appear with a URL input and an Analyze button.
- Paste a short public YouTube URL into the input field.
Before you click Analyze, what do you think will happen? Will the browser show the analysis text directly, or will it give you a link to something else?
- Click Analyze and wait 30-60 seconds for Gemini to process the video.
The button text changes to "Analyzing..." while it processes. When complete, you should see the video title in bold and a clickable "Open Google Doc" link in a green success box.
- Click the Open Google Doc link to open the generated document.
- Confirm the doc has the video title, Track 1 and Track 2 headings, and timestamped segments with all four captions (Speech Transcription, Speech Characteristics, Visuals, Audio).
What should the doc look like?
Each 30-second segment starts with a timestamp heading. Below it, "Track 1: Speech" appears as a large heading, with "Speech Transcription" and "Speech Characteristics" as sub-headings followed by their content.
Then "Track 2: Visual + Audio" appears as another large heading, with "Visuals" and "Audio" sub-headings and their content. This pattern repeats for every segment of the video.
Getting a 401 or token error?
Your session tokens expire when you restart the server (they are stored in memory). Click Sign in with Google again after each server restart to get fresh tokens.
If you see "Analysis failed" with a permissions error, make sure the Google Docs API is still enabled in your Google Cloud Console and your email is listed as a test user on the OAuth consent screen.
help me debug my Google Docs API authentication error
✔️ Awesome, I've got everything!
Great. Double check you have saved both server.js and public/index.html.
ⓧ I'd like to double check the full code
Your server.js should contain, in order:
- Imports and Express setup (unchanged from the previous step).
- OAuth2 client and GenAI initialization (unchanged).
- The /auth, /oauth2callback, and /auth-status routes (unchanged).
- The POST /analyze route with Gemini analysis PLUS the Google Docs creation code shown above.
- The buildDocRequests function (both parts shown above).
- The app.listen call at the bottom.
Your public/index.html should contain the HTML, CSS, and JavaScript shown below. Verify the key elements:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Video Analyzer</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
h1 { color: #333; }
input { width: 100%; padding: 10px; font-size: 16px; margin: 10px 0; box-sizing: border-box; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer; background: #4285f4; color: white; border: none; border-radius: 4px; }
button:disabled { background: #ccc; cursor: not-allowed; }
#result { margin-top: 20px; padding: 15px; border-radius: 4px; }
.success { background: #e6f4ea; border: 1px solid #34a853; }
.error { background: #fce8e6; border: 1px solid #ea4335; }
a { color: #4285f4; }
</style>
</head>
<body>
<h1>YouTube Video Analyzer</h1>
<div id="auth-section">
<p>Sign in with Google to create analysis documents in your Drive.</p>
<button onclick="window.location.href='/auth'">Sign in with Google</button>
</div>
<div id="analyze-section" style="display:none;">
<p>Paste a public YouTube URL to analyze:</p>
<input type="text" id="url-input" placeholder="https://www.youtube.com/watch?v=..." />
<button id="analyze-btn" onclick="analyze()">Analyze</button>
<div id="result"></div>
</div>
<script>
async function checkAuth() {
const res = await fetch("/auth-status");
const data = await res.json();
if (data.authenticated) {
document.getElementById("auth-section").style.display = "none";
document.getElementById("analyze-section").style.display = "block";
}
}
async function analyze() {
const url = document.getElementById("url-input").value;
const btn = document.getElementById("analyze-btn");
const result = document.getElementById("result");
btn.disabled = true;
btn.textContent = "Analyzing...";
result.innerHTML = "";
result.className = "";
try {
const res = await fetch("/analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ youtubeUrl: url }),
});
const data = await res.json();
if (res.ok) {
result.className = "success";
result.innerHTML = `<strong>${data.title}</strong><br><br><a href="${data.docUrl}" target="_blank">Open Google Doc</a>`;
} else {
result.className = "error";
result.textContent = data.error;
}
} catch (err) {
result.className = "error";
result.textContent = "Something went wrong. Please try again.";
}
btn.disabled = false;
btn.textContent = "Analyze";
}
checkAuth();
</script>
</body>
</html>
You have a complete video analysis tool. Paste a YouTube URL, click a button, and get a professional report in your Google Drive. Next up, give it a polished frontend with loading animations and error handling in the Secret Mission.
Secret mission
Add a Polished Frontend with Loading State
Your video analyzer works, but the UI is bare-bones. A real tool needs visual feedback during the 30-60 second analysis period and should display the video title when the report is ready. Can you add a loading spinner, client-side validation, and polished styling?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses free-tier services and runs locally, so there are no ongoing costs while the server is stopped.
Resources you used:
- Google Cloud project with OAuth2 credentials (free, no charges).
- Gemini API key from Google AI Studio (free tier: 8 hours of YouTube video per day).
- Google Docs created in your Google Drive (free, standard Drive storage).
- Local project folder (video-analyzer) on your Desktop.
Keep everything running
No action needed. Choose this if you plan to keep building on this project or want to analyze more videos later.
- Your Google Cloud project and API keys remain active at no cost.
- The Gemini free tier renews daily (8 hours of YouTube video per day).
- Nothing runs unless you start the local server with node server.js.
Pause - I'll come back to this later
Shut down running processes to free up memory. Your files and credentials stay intact for when you return.
- If the server is still running in your terminal, press Ctrl+C to stop it.
- No other processes run in the background. Your project is ready whenever you come back.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below to fully clean up.
- Delete your Gemini API key by opening console.cloud.google.com/apis/credentials in your browser. Find your API key in the list, click the Actions icon (three dots), and select Delete API key. Confirm the deletion in the dialog.
- Delete your OAuth2 credentials on the same Credentials page. Find your OAuth 2.0 Client ID under the OAuth 2.0 Client IDs section, click the Actions icon, and select Delete OAuth client. Confirm the deletion.
- Delete any generated Google Docs from your Drive. Open Google Drive, search for documents titled Video Analysis:, select them, and move them to Trash.
- Delete the local project folder by running this command in your terminal:
rm -rf ~/Desktop/video-analyzer
On Windows, run this instead:
Remove-Item -Recurse -Force ~\Desktop\video-analyzer
- Optionally, delete the entire Google Cloud project if you created it only for this tutorial. In Google Cloud Console, go to IAM & Admin then Manage resources, select your project, and click Delete.
Nice Work!
Nice Work!
You just built a fully functional YouTube video analyzer that turns any public video into a professionally formatted Google Doc report. From setting up OAuth2 credentials to crafting multimodal AI prompts, you orchestrated multiple Google APIs into a single working tool.
Here's what you learned:
- Built a YouTube video analyzer that sends videos to Gemini AI for multimodal analysis and outputs timestamped two-track breakdowns covering speech, visuals, and audio.
- Configured Google Cloud OAuth2 authentication from scratch so the app can create formatted Google Docs in a user's Drive on their behalf.
- Used GitHub Copilot Chat to generate a complete Node.js Express app through 3 targeted prompts, integrating the Gemini API and the Google Docs API in a single application.
- Secret Mission: Added a polished frontend with a loading spinner, video title display, and user-friendly error handling.
Ready to quiz yourself?