Created
April 11, 2025 13:16
-
-
Save andypiper/f165a38ee270ddbf4dfa900e0cd3fd1f to your computer and use it in GitHub Desktop.
Google Slides export speaker notes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Creates a custom menu in the Google Slides UI when the presentation is opened. | |
*/ | |
function onOpen() { | |
SlidesApp.getUi() | |
.createMenu('Speaker Tools') | |
.addItem('Export with previews', 'initiateNotesExportWithPreviews') | |
.addItem('Export without previews', 'initiateNotesExportWithoutPreviews') | |
.addItem('Export text only', 'initiateNotesExportTextOnly') | |
.addToUi(); | |
} | |
// --- Initiator Functions --- | |
/** | |
* Initiates the export process for notes WITH slide previews. | |
*/ | |
function initiateNotesExportWithPreviews() { | |
initiateNotesExport('with_previews', createNotesDocWithPreviews); | |
} | |
/** | |
* Initiates the export process for notes WITHOUT slide previews (table format). | |
*/ | |
function initiateNotesExportWithoutPreviews() { | |
initiateNotesExport('without_previews', createNotesDocWithoutPreviews); | |
} | |
/** | |
* Initiates the export process for notes as TEXT ONLY (numbered). | |
*/ | |
function initiateNotesExportTextOnly() { | |
initiateNotesExport('text_only', createNotesDocTextOnly); | |
} | |
/** | |
* Helper function to handle the common parts of initiating an export: | |
* getting presentation info, prompting for doc name, creating doc, and calling | |
* the specific export function. | |
* | |
* @param {string} formatType A string identifier for the format (e.g., 'with_previews'). | |
* @param {function} exportFunction The specific core export function to call. | |
*/ | |
function initiateNotesExport(formatType, exportFunction) { | |
var ui = SlidesApp.getUi(); | |
var presentation = SlidesApp.getActivePresentation(); | |
var presentationId = presentation.getId(); | |
var presentationName = presentation.getName(); | |
// Determine default document name based on format | |
var formatDesc = formatType.replace('_', ' '); // e.g., "with previews" | |
var defaultDocName = `${presentationName} - Speaker Notes (${formatDesc})`; | |
var promptResponse = ui.prompt( | |
`Export Notes (${formatDesc})`, | |
'Enter a name for the new notes Doc:', | |
ui.ButtonSet.OK_CANCEL); | |
if (promptResponse.getSelectedButton() == ui.Button.OK) { | |
var docName = promptResponse.getResponseText() || defaultDocName; // Use default if empty | |
try { | |
Logger.log(`Creating new document for format [${formatType}]: ${docName}`); | |
var newDoc = DocumentApp.create(docName); | |
var outputDocId = newDoc.getId(); | |
var newDocUrl = newDoc.getUrl(); | |
Logger.log(`New document created with ID: ${outputDocId}`); | |
// Show progress sidebar | |
var sidebarHtml = `<p>Exporting notes (${formatDesc})... Please wait.</p>`; | |
ui.showSidebar(HtmlService.createHtmlOutput(sidebarHtml).setTitle('Processing')); | |
// Call the specific export function passed as an argument | |
exportFunction(presentationId, outputDocId); // Pass IDs | |
// Show completion message | |
var completionHtml = `<p>Export complete! (${formatDesc})<br/><a href="${newDocUrl}" target="_blank">Click here to open the document.</a></p><br/><button onclick="google.script.host.close()">Close</button>`; | |
ui.showSidebar(HtmlService.createHtmlOutput(completionHtml).setTitle('Success')); | |
Logger.log(`Notes export (${formatType}) completed successfully.`); | |
} catch (e) { | |
Logger.log(`Error during notes export process (${formatType}): ${e}\nStack: ${e.stack}`); | |
ui.alert(`An error occurred during the export (${formatDesc}): ${e.message}`); | |
// Close sidebar on error | |
try { ui.showSidebar(HtmlService.createHtmlOutput('<p>An error occurred. Please check logs.</p><br/><button onclick="google.script.host.close()">Close</button>').setTitle('Error')); } catch (sidebarError) {} | |
} | |
} else { | |
Logger.log(`User cancelled the export (${formatType}).`); | |
ui.alert(`Notes export (${formatDesc}) cancelled.`); | |
} | |
} | |
// --- Core Export Functions --- | |
/** | |
* Exports notes WITH PREVIEWS to a table. (Original logic) | |
* @param {string} presentationId The ID of the Google Slides presentation. | |
* @param {string} outputDocId The ID of the Google Document to export notes to. | |
*/ | |
function createNotesDocWithPreviews(presentationId, outputDocId) { | |
var presentation = SlidesApp.openById(presentationId); | |
var out = DocumentApp.openById(outputDocId); | |
var body = out.getBody(); | |
body.clear(); | |
var table = body.appendTable(); | |
var baseUrl = "https://slides.googleapis.com/v1/presentations/{presentationId}/pages/{pageObjectId}/thumbnail"; | |
var parameters = { | |
method: "GET", | |
headers: { "Authorization": "Bearer " + ScriptApp.getOAuthToken() }, | |
muteHttpExceptions: true, | |
contentType: "application/json" | |
}; | |
var slides = presentation.getSlides(); | |
Logger.log(`[With Previews] Starting export. Total slides: ${slides.length}`); | |
for (var i = 0; i < slides.length; i++) { | |
var slide = slides[i]; | |
if (slide.isSkipped()) { | |
Logger.log(`[With Previews] Skipping slide ${i + 1} (Skipped)`); | |
continue; | |
} | |
Logger.log(`[With Previews] Processing slide ${i + 1}`); | |
var text = slide.getNotesPage().getSpeakerNotesShape().getText().asRenderedString(); | |
var row = table.appendTableRow(); | |
var slideObjectId = slide.getObjectId(); | |
var url = baseUrl.replace("{presentationId}", presentationId).replace("{pageObjectId}", slideObjectId); | |
// Image Cell | |
var imageCell = row.appendTableCell(); | |
if (imageCell.getNumChildren() > 0 && imageCell.getChild(0).getType() === DocumentApp.ElementType.PARAGRAPH && imageCell.getChild(0).asParagraph().getText() === "") { | |
imageCell.removeChild(imageCell.getChild(0)); | |
} | |
try { | |
var response = UrlFetchApp.fetch(url, parameters); | |
var responseCode = response.getResponseCode(); | |
var responseBody = response.getContentText(); | |
if (responseCode === 200) { | |
var responseJson = JSON.parse(responseBody); | |
if (responseJson && responseJson.contentUrl) { | |
var imageBlob = UrlFetchApp.fetch(responseJson.contentUrl).getBlob(); | |
imageCell.appendImage(imageBlob).setWidth(160).setHeight(90); | |
} else { Logger.log(`[With Previews] No contentUrl for slide ${i + 1}. Response: ${responseBody}`); imageCell.appendParagraph("[No URL]"); } | |
} else { Logger.log(`[With Previews] Error fetching thumb ${i + 1}. Code: ${responseCode}. Body: ${responseBody}`); imageCell.appendParagraph(`[Err: ${responseCode}]`); } | |
} catch (e) { Logger.log(`[With Previews] Exception fetching thumb ${i + 1}: ${e}`); imageCell.appendParagraph("[Fetch Err]"); } | |
// Note Cell | |
var noteCell = row.appendTableCell(); | |
if (noteCell.getNumChildren() > 0 && noteCell.getChild(0).getType() === DocumentApp.ElementType.PARAGRAPH && noteCell.getChild(0).asParagraph().getText() === "") { | |
noteCell.removeChild(noteCell.getChild(0)); | |
} | |
noteCell.appendParagraph(text || ""); // Append empty string if text is null/undefined | |
} | |
if (table.getNumRows() > 0) { | |
table.setColumnWidth(0, 160); // Image column width | |
} | |
out.saveAndClose(); | |
Logger.log(`[With Previews] Finished export to doc ID: ${outputDocId}`); | |
} | |
/** | |
* Exports notes WITHOUT PREVIEWS to a table. | |
* @param {string} presentationId The ID of the Google Slides presentation. | |
* @param {string} outputDocId The ID of the Google Document to export notes to. | |
*/ | |
function createNotesDocWithoutPreviews(presentationId, outputDocId) { | |
var presentation = SlidesApp.openById(presentationId); | |
var out = DocumentApp.openById(outputDocId); | |
var body = out.getBody(); | |
body.clear(); | |
var table = body.appendTable([['Slide Notes']]); // Add header row | |
// Optional: format header | |
table.getRow(0).getCell(0).editAsText().setBold(true); | |
var slides = presentation.getSlides(); | |
Logger.log(`[Without Previews] Starting export. Total slides: ${slides.length}`); | |
for (var i = 0; i < slides.length; i++) { | |
var slide = slides[i]; | |
if (slide.isSkipped()) { | |
Logger.log(`[Without Previews] Skipping slide ${i + 1} (Skipped)`); | |
continue; | |
} | |
Logger.log(`[Without Previews] Processing slide ${i + 1}`); | |
var text = slide.getNotesPage().getSpeakerNotesShape().getText().asRenderedString(); | |
var row = table.appendTableRow(); | |
// Note Cell Only | |
var noteCell = row.appendTableCell(); | |
noteCell.appendParagraph(text || ""); // Append empty string if text is null/undefined | |
} | |
// No column width setting needed unless you want to constrain the notes column | |
out.saveAndClose(); | |
Logger.log(`[Without Previews] Finished export to doc ID: ${outputDocId}`); | |
} | |
/** | |
* Exports notes as TEXT ONLY with numbered headings. | |
* @param {string} presentationId The ID of the Google Slides presentation. | |
* @param {string} outputDocId The ID of the Google Document to export notes to. | |
*/ | |
function createNotesDocTextOnly(presentationId, outputDocId) { | |
var presentation = SlidesApp.openById(presentationId); | |
var out = DocumentApp.openById(outputDocId); | |
var body = out.getBody(); | |
body.clear(); | |
var slides = presentation.getSlides(); | |
Logger.log(`[Text Only] Starting export. Total slides: ${slides.length}`); | |
var actualSlideNumber = 0; // To handle skipped slides correctly in numbering | |
for (var i = 0; i < slides.length; i++) { | |
var slide = slides[i]; | |
if (slide.isSkipped()) { | |
Logger.log(`[Text Only] Skipping slide ${i + 1} (Skipped)`); | |
continue; | |
} | |
actualSlideNumber++; // Increment only for non-skipped slides | |
Logger.log(`[Text Only] Processing slide ${i + 1} as Export Slide #${actualSlideNumber}`); | |
var text = slide.getNotesPage().getSpeakerNotesShape().getText().asRenderedString(); | |
// Add Heading for the slide number | |
body.appendParagraph(`Slide ${actualSlideNumber}`) | |
.setHeading(DocumentApp.ParagraphHeading.HEADING2); // Or HEADING1, HEADING3 etc. | |
// Add notes text | |
body.appendParagraph(text || "[No speaker notes for this slide]"); // Add placeholder if empty | |
// Add separator (optional) | |
body.appendParagraph(""); // Add a blank line for spacing | |
// or body.appendHorizontalRule(); | |
} | |
out.saveAndClose(); | |
Logger.log(`[Text Only] Finished export to doc ID: ${outputDocId}`); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment