Last active
July 7, 2025 12:34
-
-
Save nxnarbais/3ad3ebc4ed67bb3f4b0e435b002ae9c4 to your computer and use it in GitHub Desktop.
Revisions
-
nxnarbais revised this gist
Jul 7, 2025 . 1 changed file with 6 additions and 2 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,10 +1,14 @@ // Gist: https://gist.github.com/nxnarbais/3ad3ebc4ed67bb3f4b0e435b002ae9c4 /** * Constants and Configuration */ const SYNTHESIA_API_KEY = ""; const SHEET_OUTPUT_VIDEO = 'output'; const SYNTHESIA_API_URL = 'https://api.synthesia.io/v2/videos/fromTemplate'; const COLUMNS_TO_EXCLUDE = ["_to_json", "template_id", "title", "description", "section", "lineIndex", "cta_label", "cta_url"]; const OUTPUT_COLUMNS_MANDATORY = ["template_id", "title", "lineIndex", "videoID", "publicVideoLink"]; const TEST = "false"; // Logging configuration const LOG_LEVELS = { @@ -138,7 +142,7 @@ function generateVideos() { throw new Error('Failed to create output sheet'); } const titles = [...new Set(Object.keys(parsedRows[0]).concat(OUTPUT_COLUMNS_MANDATORY))]; const rows = parsedRows.map(parsedRow => titles.map(title => parsedRow[title]) ); @@ -272,7 +276,7 @@ function generateRequest(parsedRow) { }); const jsonData = { test: TEST, templateId: template_id, templateData: templateData, title: title, -
nxnarbais created this gist
May 5, 2025 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,423 @@ /** * Constants and Configuration */ const SYNTHESIA_API_KEY = ""; const SHEET_OUTPUT_VIDEO = 'output'; const SYNTHESIA_API_URL = 'https://api.synthesia.io/v2/videos/fromTemplate'; const COLUMNS_TO_EXCLUDE = ["_to_json", "template_id", "title", "description", "section", "lineIndex", "cta_label", "cta_url"]; // Logging configuration const LOG_LEVELS = { DEBUG: 'DEBUG', INFO: 'INFO', WARN: 'WARN', ERROR: 'ERROR' }; function log(level, message, data = null) { const timestamp = new Date().toISOString(); const logMessage = `[${timestamp}] [${level}] ${message}`; if (data) { Logger.log(`${logMessage}\nData: ${JSON.stringify(data, null, 2)}`); } else { Logger.log(logMessage); } } /** * Menu and High Level Functions */ function onOpen() { try { var ui = SpreadsheetApp.getUi(); ui.createMenu('Synthesia') .addItem('Generate videos', 'generateVideos') .addToUi(); } catch (error) { Logger.log(`Error in onOpen: ${error.message}`); throw error; } } function generateVideos() { try { log(LOG_LEVELS.INFO, 'Starting video generation process'); // Validate API key if (!getSynthesiaAPIKey()) { log(LOG_LEVELS.ERROR, 'Synthesia API key is not set'); throw new Error('Synthesia API key is not set'); } const sheet = SpreadsheetApp.getActiveSheet(); if (!sheet) { log(LOG_LEVELS.ERROR, 'No active sheet found'); throw new Error('No active sheet found'); } log(LOG_LEVELS.INFO, 'Reading table values from sheet', { sheetName: sheet.getName() }); const tableValues = getTableValues(sheet); log(LOG_LEVELS.DEBUG, 'Table values retrieved', { rowCount: tableValues.length }); const parsedRows = parseRows(tableValues); log(LOG_LEVELS.INFO, 'Parsed rows', { totalRows: parsedRows.length, firstRowKeys: Object.keys(parsedRows[0] || {}) }); if (!parsedRows || parsedRows.length === 0) { log(LOG_LEVELS.WARN, 'No valid rows found to process'); SpreadsheetApp.getUi().alert('No valid rows found to process'); return; } let successCount = 0; let errorCount = 0; parsedRows.forEach((row, index) => { try { log(LOG_LEVELS.INFO, `Processing row ${index + 1}/${parsedRows.length}`, { templateId: row.template_id, title: row.title }); const response = generateVideoFromTemplate(row); const responseCode = response.getResponseCode(); log(LOG_LEVELS.DEBUG, `API Response for row ${index + 1}`, { responseCode, responseText: response.getContentText() }); if (responseCode !== 201) { log(LOG_LEVELS.ERROR, `Error generating video for row ${index + 1}`, { responseCode, responseText: response.getContentText() }); errorCount++; return; } const responseBodyJSON = JSON.parse(response.getContentText()); if (!responseBodyJSON.id) { log(LOG_LEVELS.ERROR, `No video ID in response for row ${index + 1}`, { responseBody: responseBodyJSON }); errorCount++; return; } row.videoID = responseBodyJSON.id; row.publicVideoLink = `https://share.synthesia.io/${responseBodyJSON.id}`; log(LOG_LEVELS.INFO, `Successfully generated video for row ${index + 1}`, { videoId: responseBodyJSON.id, publicLink: row.publicVideoLink }); successCount++; } catch (error) { log(LOG_LEVELS.ERROR, `Error processing row ${index + 1}`, { error: error.message, rowData: row }); errorCount++; } }); log(LOG_LEVELS.INFO, 'Creating output sheet', { sheetName: SHEET_OUTPUT_VIDEO, successCount, errorCount }); const outputSheet = getOrCreateSheet(SHEET_OUTPUT_VIDEO, 10, 4); if (!outputSheet) { log(LOG_LEVELS.ERROR, 'Failed to create output sheet'); throw new Error('Failed to create output sheet'); } const titles = Object.keys(parsedRows[0]); const rows = parsedRows.map(parsedRow => titles.map(title => parsedRow[title]) ); log(LOG_LEVELS.DEBUG, 'Preparing to fill output sheet', { rowCount: rows.length, columnCount: titles.length }); fillTable(outputSheet, rows, titles); const ui = SpreadsheetApp.getUi(); const message = `Video generation complete:\n${successCount} videos created successfully\n${errorCount} videos failed`; log(LOG_LEVELS.INFO, message); ui.alert(message); } catch (error) { log(LOG_LEVELS.ERROR, 'Error in generateVideos', { error: error.message, stack: error.stack }); SpreadsheetApp.getUi().alert(`Error generating videos: ${error.message}`); } } /** * Synthesia API Functions */ function getSynthesiaAPIKey() { if (!SYNTHESIA_API_KEY) { Logger.log('Warning: Synthesia API key is not set'); return null; } return SYNTHESIA_API_KEY; } function getCallbackId(parsedRow) { if (!parsedRow || !parsedRow.template_id) { throw new Error('Invalid row data for callback ID'); } const callbackId = { template_id: parsedRow.template_id }; Object.keys(parsedRow).forEach(key => { if (!COLUMNS_TO_EXCLUDE.includes(key) && key.startsWith("_")) { callbackId[key] = parsedRow[key]; } }); return callbackId; } function getTemplateData(parsedRow) { if (!parsedRow) { throw new Error('Invalid row data for template data'); } const templateData = {}; Object.keys(parsedRow).forEach(key => { if (!COLUMNS_TO_EXCLUDE.includes(key) && !key.startsWith("_")) { templateData[key] = parsedRow[key]; } }); return templateData; } function getCtaSettings(parsedRow) { if (!parsedRow) { return null; } if (parsedRow.cta_label && parsedRow.cta_url) { return { label: parsedRow.cta_label, url: parsedRow.cta_url }; } return null; } function generateVideoFromTemplate(parsedRow) { try { log(LOG_LEVELS.DEBUG, 'Generating video from template', { templateId: parsedRow.template_id, title: parsedRow.title }); const request = generateRequest(parsedRow); log(LOG_LEVELS.DEBUG, 'Generated API request', { url: request.url, method: request.method, payload: JSON.parse(request.payload) }); const responses = UrlFetchApp.fetchAll([request]); const response = responses[0]; log(LOG_LEVELS.DEBUG, 'Received API response', { responseCode: response.getResponseCode(), responseText: response.getContentText() }); return response; } catch (error) { log(LOG_LEVELS.ERROR, 'Error in generateVideoFromTemplate', { error: error.message, templateId: parsedRow.template_id }); throw error; } } function generateRequest(parsedRow) { if (!parsedRow || !parsedRow.template_id) { log(LOG_LEVELS.ERROR, 'Invalid row data for video generation', { row: parsedRow }); throw new Error('Invalid row data for video generation'); } try { const { template_id, title, description } = parsedRow; const templateData = getTemplateData(parsedRow); const callbackId = getCallbackId(parsedRow); log(LOG_LEVELS.DEBUG, 'Preparing request data', { templateId: template_id, title, templateData, callbackId }); const jsonData = { test: "false", templateId: template_id, templateData: templateData, title: title, description: description || "", visibility: "public", callbackId: JSON.stringify(callbackId) }; const ctaSettings = getCtaSettings(parsedRow); if (ctaSettings) { jsonData.ctaSettings = ctaSettings; log(LOG_LEVELS.DEBUG, 'Added CTA settings to request', { ctaSettings }); } return { url: SYNTHESIA_API_URL, method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': getSynthesiaAPIKey() }, payload: JSON.stringify(jsonData) }; } catch (error) { log(LOG_LEVELS.ERROR, 'Error in generateRequest', { error: error.message, templateId: parsedRow.template_id }); throw error; } } /** * Sheet Manipulation Functions */ function getOrCreateSheet(sheetName, desiredRows, desiredColumns) { if (!sheetName) { throw new Error('Sheet name is required'); } try { const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); if (!spreadsheet) { throw new Error('No active spreadsheet found'); } const sheets = spreadsheet.getSheets(); const sheetExists = sheets.some(sheet => sheet.getName() === sheetName); if (!sheetExists) { const newSheet = spreadsheet.insertSheet(sheetName); if (desiredRows) { newSheet.deleteRows(desiredRows + 1, newSheet.getMaxRows() - desiredRows); } if (desiredColumns) { newSheet.deleteColumns(desiredColumns + 1, newSheet.getMaxColumns() - desiredColumns); } } return spreadsheet.getSheetByName(sheetName); } catch (error) { Logger.log(`Error in getOrCreateSheet: ${error.message}`); throw error; } } function fillTable(sheet, rows, titles) { if (!sheet || !rows || rows.length === 0) { throw new Error('Invalid parameters for fillTable'); } try { if (titles && titles.length > 0) { sheet.getRange(1, 1, 1, titles.length).setValues([titles]); } sheet.getRange(2, 1, rows.length, rows[0].length).setValues(rows); } catch (error) { Logger.log(`Error in fillTable: ${error.message}`); throw error; } } function getTableValues(sheet) { if (!sheet) { throw new Error('No sheet provided'); } return sheet.getDataRange().getValues(); } function isRowEmpty(row) { if (!row || !Array.isArray(row)) { return true; } return row.every(cell => cell === ""); } function isRowSection(row) { // Implement section detection logic if needed return false; } function parseRow(row, attributeNames, section, lineIndex) { if (!row || !attributeNames) { throw new Error('Invalid parameters for parseRow'); } try { const parsedRow = {}; row.forEach((cell, index) => { parsedRow[attributeNames[index]] = cell; }); parsedRow.section = section; parsedRow.lineIndex = lineIndex; return parsedRow; } catch (error) { Logger.log(`Error in parseRow: ${error.message}`); throw error; } } function parseRows(rows) { if (!rows || rows.length < 2) { throw new Error('Invalid rows data'); } try { const attributeNames = rows[0]; const parsedRows = []; let section = ""; for (let i = 1; i < rows.length; i++) { const row = rows[i]; if (isRowEmpty(row)) { continue; } if (isRowSection(row)) { section = row[0]; continue; } parsedRows.push(parseRow(row, attributeNames, section, i)); } return parsedRows; } catch (error) { Logger.log(`Error in parseRows: ${error.message}`); throw error; } }