{ "id": "f9X48gqgIUwyseMM", "meta": { "instanceId": "d47f3738b860eed937a1b18d7345fa2c65cf4b4957554e29477cb064a7039870" }, "name": "Obsidian Notes Read Aloud: Available as a Podcast Feed", "tags": [], "nodes": [ { "id": "a44b5cb3-6c9f-4227-a45f-a21765ea120c", "name": "OpenAI1", "type": "@n8n/n8n-nodes-langchain.openAi", "position": [ -660, -180 ], "parameters": { "input": "={{ $json.body.content }}", "options": { "response_format": "mp3" }, "resource": "audio" }, "credentials": { "openAiApi": { "id": "q8L9oWVM7QyzYEE5", "name": "OpenAi account" } }, "typeVersion": 1.7 }, { "id": "9ca589b6-f1c7-44a9-8ff7-4abb979a71c3", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [ -1200, -400 ], "parameters": { "width": 440, "height": 540, "content": "## Send Notes to Webhook\n**Setup:**\n- Install [Post Webhook Plugin](https://github.com/Masterb1234/obsidian-post-webhook/) in Obsidian\n- Enter n8n Webhook URL and name in plugin settings\n\n**Usage:**\n- Select text or use full note\n- Open Command Palette (Ctrl+P)\n- Choose 'Send Note/Selection to [name]'\n- Audio file appears in Podcast Feed and note" }, "typeVersion": 1 }, { "id": "3ea132e5-8c67-4140-a9b2-607ea256e90f", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "position": [ -1200, 240 ], "parameters": { "width": 440, "height": 440, "content": "## Generic Podcast Feed Module\nA reusable module for any 'X-to-Podcast' workflow. Generates standard RSS feed from:\n- Source data (Google Sheets)\n- Podcast metadata\n\nCompatible with all major podcast platforms (Apple, Google, Spotify, etc.).\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" }, "typeVersion": 1 }, { "id": "92d6a6df-0e4e-423b-8447-dce10d5373ae", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [ -720, -400 ], "parameters": { "color": 3, "width": 440, "height": 540, "content": "## Create Audio and Write Description\nOpenAI TTS converts notes to audio while the messaging model generates concise descriptions for podcast apps." }, "typeVersion": 1 }, { "id": "b950b0ab-e27e-473d-9891-d5551a44ed17", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [ 800, -400 ], "parameters": { "color": 4, "width": 380, "height": 540, "content": "## Append Row to Google Sheets\nSaves essential podcast parameters (, <link>, <description>, <duration>) to Google Sheets for Feed generation." }, "typeVersion": 1 }, { "id": "02fda37f-77a5-47f5-81bc-b59486704386", "name": "Webhook GET Note", "type": "n8n-nodes-base.webhook", "position": [ -1040, -120 ], "webhookId": "64fac784-9b98-4bbc-aaf2-dd45763d3362", "parameters": { "path": "64fac784-9b98-4bbc-aaf2-dd45763d3362", "options": {}, "httpMethod": "POST", "responseMode": "responseNode" }, "typeVersion": 2 }, { "id": "845d04ea-d221-4034-b5e1-75061e5f351c", "name": "Webhook GET Podcast Feed", "type": "n8n-nodes-base.webhook", "position": [ -1040, 460 ], "webhookId": "2f0a6706-54da-4b89-91f4-5e147b393bd8", "parameters": { "path": "2f0a6706-54da-4b89-91f4-5e147b393bd8h", "options": {}, "responseMode": "responseNode" }, "typeVersion": 2 }, { "id": "ce6d766c-89e6-4d62-9d48-d6715a28592f", "name": "Upload Audio to Cloudinary", "type": "n8n-nodes-base.httpRequest", "position": [ -220, -120 ], "parameters": { "url": "https://api.cloudinary.com/v1_1/CLOUDINARY_ENV/upload", "method": "POST", "options": {}, "sendBody": true, "contentType": "multipart-form-data", "sendHeaders": true, "authentication": "genericCredentialType", "bodyParameters": { "parameters": [ { "name": "file", "parameterType": "formBinaryData", "inputDataFieldName": "data" }, { "name": "upload_preset", "value": "rb_preset" }, { "name": "resource_type", "value": "auto" } ] }, "genericAuthType": "httpCustomAuth", "headerParameters": { "parameters": [ { "name": "Content-Type", "value": "multipart/form-data" } ] } }, "credentials": { "httpCustomAuth": { "id": "DHmR14pD9rTrd3nS", "name": "Cloudinary API" } }, "typeVersion": 4.1 }, { "id": "1f86c18d-8197-4671-9c41-726a02108c4e", "name": "OpenAI", "type": "@n8n/n8n-nodes-langchain.openAi", "position": [ -660, -20 ], "parameters": { "modelId": { "__rl": true, "mode": "list", "value": "gpt-4o-mini", "cachedResultName": "GPT-4O-MINI" }, "options": {}, "messages": { "values": [ { "content": "={{ $json.body.content }}" }, { "role": "system", "content": "Based on the user input text, write a concise and engaging description of 50–150 characters. Highlight the key idea or takeaway while making it compelling and easy to understand. Avoid unnecessary details or repetition." } ] } }, "credentials": { "openAiApi": { "id": "q8L9oWVM7QyzYEE5", "name": "OpenAi account" } }, "typeVersion": 1.7 }, { "id": "0942959c-2231-4055-b196-4483c210a39d", "name": "Merge", "type": "n8n-nodes-base.merge", "position": [ 320, -40 ], "parameters": {}, "typeVersion": 3 }, { "id": "ee7ba6a7-f8dd-4863-bf5c-6ec8eb2329ea", "name": "Aggregate", "type": "n8n-nodes-base.aggregate", "position": [ 460, -180 ], "parameters": { "options": {}, "aggregate": "aggregateAllItemData" }, "typeVersion": 1 }, { "id": "f403d045-08e9-400e-9988-c8f55a5aa609", "name": "Give Audio Unique Name", "type": "n8n-nodes-base.set", "position": [ -460, -180 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "97f0fe66-7ddf-4eff-a3cf-3104e74dbfac", "name": "fileName", "type": "string", "value": "={{ $('Webhook GET Note').item.json.body.timestamp }}.mp3" } ] }, "includeOtherFields": true }, "typeVersion": 3.4 }, { "id": "2dbff0f5-f359-43b7-b0de-4b9d657c69c0", "name": "Send Audio to Obsidian", "type": "n8n-nodes-base.respondToWebhook", "position": [ 80, -180 ], "parameters": { "options": { "responseHeaders": { "entries": [ { "name": "content-type", "value": "=audio/mpeg" } ] } }, "respondWith": "binary", "responseDataSource": "set" }, "typeVersion": 1 }, { "id": "ede7c038-b210-4b29-8557-7530ea4cf63e", "name": "Rename Fields", "type": "n8n-nodes-base.set", "position": [ 620, -180 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "3a7d01f4-7448-40e0-9f46-e6edea971b72", "name": "title", "type": "string", "value": "={{ $('Webhook GET Note').item.json.body.filename.split('.md')[0] }}" }, { "id": "f49446df-3975-4133-a964-ebdcc0d904dd", "name": "link", "type": "string", "value": "={{ $json.data[0].url }}" }, { "id": "8be5df35-ec79-45b1-94c3-306d58100fd2", "name": "description", "type": "string", "value": "={{ $json.data[1].message.content }}" }, { "id": "231d0ee2-13d2-4a28-a19c-adc4920130fd", "name": "date", "type": "string", "value": "={{ $json.data[0].created_at }}" }, { "id": "cd2748b3-999a-4514-9b31-49b7d045101f", "name": "duration", "type": "number", "value": "={{ $json.data[0].duration }}" } ] } }, "typeVersion": 3.4 }, { "id": "10a35ef9-ab86-4010-9fcc-3cd765384e93", "name": "Append Item to Google Sheet", "type": "n8n-nodes-base.googleSheets", "position": [ 940, -180 ], "parameters": { "columns": { "value": {}, "schema": [ { "id": "title", "type": "string", "display": true, "removed": false, "required": false, "displayName": "title", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "link", "type": "string", "display": true, "removed": false, "required": false, "displayName": "link", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "description", "type": "string", "display": true, "removed": false, "required": false, "displayName": "description", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "date", "type": "string", "display": true, "removed": false, "required": false, "displayName": "date", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "duration", "type": "string", "display": true, "removed": false, "required": false, "displayName": "duration", "defaultMatch": false, "canBeUsedToMatch": true } ], "mappingMode": "autoMapInputData", "matchingColumns": [] }, "options": {}, "operation": "append", "sheetName": { "__rl": true, "mode": "list", "value": "gid=0", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F73a7uuzLAq916w2JFndumv0JhnCAvOTN-Cn_OOP3uA/edit#gid=0", "cachedResultName": "Blad1" }, "documentId": { "__rl": true, "mode": "list", "value": "1F73a7uuzLAq916w2JFndumv0JhnCAvOTN-Cn_OOP3uA", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F73a7uuzLAq916w2JFndumv0JhnCAvOTN-Cn_OOP3uA/edit?usp=drivesdk", "cachedResultName": "obsidian-n8n" } }, "credentials": { "googleSheetsOAuth2Api": { "id": "3Pu0wlfxgNYzVqY6", "name": "Google Sheets account" } }, "typeVersion": 4.5 }, { "id": "62dd3faf-22db-40f9-892c-2cf9368a9496", "name": "Get Items from Google Sheets", "type": "n8n-nodes-base.googleSheets", "position": [ -660, 460 ], "parameters": { "options": {}, "sheetName": { "__rl": true, "mode": "list", "value": "gid=0", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F73a7uuzLAq916w2JFndumv0JhnCAvOTN-Cn_OOP3uA/edit#gid=0", "cachedResultName": "Blad1" }, "documentId": { "__rl": true, "mode": "list", "value": "1F73a7uuzLAq916w2JFndumv0JhnCAvOTN-Cn_OOP3uA", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1F73a7uuzLAq916w2JFndumv0JhnCAvOTN-Cn_OOP3uA/edit?usp=drivesdk", "cachedResultName": "obsidian-n8n" } }, "credentials": { "googleSheetsOAuth2Api": { "id": "3Pu0wlfxgNYzVqY6", "name": "Google Sheets account" } }, "typeVersion": 4.5 }, { "id": "7b465ed0-d2cc-4862-b0e6-4bd6215f3945", "name": "Sticky Note4", "type": "n8n-nodes-base.stickyNote", "position": [ -720, 320 ], "parameters": { "color": 3, "width": 440, "height": 360, "content": "## Podcast Feed Configuration\n- Static: Configure podcast metadata in 'Edit Fields'\n- Dynamic: Episodes automatically pulled from Google Sheets" }, "typeVersion": 1 }, { "id": "1608ce65-bf1f-4dce-b4c7-b85b72ecb8c7", "name": "Write RSS Feed", "type": "n8n-nodes-base.code", "position": [ -120, 460 ], "parameters": { "jsCode": "// Variables from a separate edit node\nconst baseUrl = $node[\"Manually Enter Other Data for Podcast Feed\"].data.baseUrl; \nconst podcastTitle = $node[\"Manually Enter Other Data for Podcast Feed\"].data.podcastTitle;\nconst podcastDescription = $node[\"Manually Enter Other Data for Podcast Feed\"].data.podcastDescription;\nconst authorName = $node[\"Manually Enter Other Data for Podcast Feed\"].data.authorName;\nconst ownerName = $node[\"Manually Enter Other Data for Podcast Feed\"].data.ownerName;\nconst ownerEmail = $node[\"Manually Enter Other Data for Podcast Feed\"].data.ownerEmail;\nconst coverImageUrl = $node[\"Manually Enter Other Data for Podcast Feed\"].data.coverImageUrl;\nconst language = $node[\"Manually Enter Other Data for Podcast Feed\"].data.language || 'en-us';\nconst explicitContent = $node[\"Manually Enter Other Data for Podcast Feed\"].data.explicitContent || false;\nconst itunesCategory = $node[\"Manually Enter Other Data for Podcast Feed\"].data.itunesCategory;\nconst webhookUrl = $node[\"Webhook GET Podcast Feed\"].data.webhookUrl\n\n// Get the input items\nconst inputItems = items;\n\n// Function to format date to RFC 822 format\nfunction formatDate(dateString) {\n return new Date(dateString || new Date()).toUTCString();\n}\n\n// Function to convert duration from seconds to HH:MM:SS\nfunction formatDuration(seconds = 0) {\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const remainingSeconds = Math.floor(seconds % 60);\n\n const minutesStr = minutes.toString().padStart(2, '0');\n const secondsStr = remainingSeconds.toString().padStart(2, '0');\n \n if (hours > 0) {\n return `${hours}:${minutesStr}:${secondsStr}`;\n }\n return `${minutesStr}:${secondsStr}`;\n}\n\n// Function to safely sanitize text\nfunction sanitizeText(text) {\n if (text === undefined || text === null) {\n return '';\n }\n return String(text)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n// Generate the RSS feed header\nlet rssFeed = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\" \n xmlns:content=\"http://purl.org/rss/1.0/modules/content/\"\n xmlns:atom=\"http://www.w3.org/2005/Atom\"\n version=\"2.0\">\n <channel>\n <title>${sanitizeText(podcastTitle)}\n ${sanitizeText(podcastDescription)}\n ${sanitizeText(baseUrl)}\n \n ${sanitizeText(language)}\n © ${new Date().getFullYear()} ${sanitizeText(authorName)}\n ${new Date().toUTCString()}\n ${sanitizeText(authorName)}\n \n ${sanitizeText(ownerName)}\n ${sanitizeText(ownerEmail)}\n \n \n \n ${explicitContent}\n episodic\\n`;\n\n// Generate items\nfor (const item of inputItems) {\n const json = item.json;\n \n // Extract values from the json object\n const title = sanitizeText(json.title);\n const description = sanitizeText(json.description);\n const link = sanitizeText(json.link);\n const date = json.date;\n const duration = json.duration;\n \n // Assign episode and season numbers dynamically based on row_number\n const episodeNumber = json.row_number; // Use row_number for the episode number\n const seasonNumber = 1; // You can adjust this logic if your episodes span multiple seasons\n\n rssFeed += ` \n ${title}\n ${description}\n ${link}\n ${link}\n ${formatDate(date)}\n \n ${formatDuration(duration)}\n ${description}\n full\n ${episodeNumber}\n ${seasonNumber}\n ${explicitContent}\n \n ${description}

\n ]]>\n
\n
\\n`;\n}\n\n// Close the RSS feed\nrssFeed += ` \n`;\n\n// Return the complete RSS feed\nreturn [{\n json: {\n rssFeed\n }\n}];\n" }, "typeVersion": 2 }, { "id": "c8c7fbfc-c408-438e-af7e-5c384cfce4a5", "name": "Sticky Note6", "type": "n8n-nodes-base.stickyNote", "position": [ -240, 320 ], "parameters": { "color": 5, "width": 340, "height": 360, "content": "## Write Podcast Feed\nGenerates RSS feed XML from collected data." }, "typeVersion": 1 }, { "id": "b5962e24-49eb-423a-ab8c-cb04daf5e1a0", "name": "Sticky Note7", "type": "n8n-nodes-base.stickyNote", "position": [ -240, -400 ], "parameters": { "color": 5, "width": 460, "height": 540, "content": "## Audio to Cloudinary and Obsidian\nCloudinary stores audio files and provides duration metadata for podcast feed.\n\nSetup:\n- Create Custom Auth credentials\n- Set CLOUDINARY_ENV to your environment" }, "typeVersion": 1 }, { "id": "e0f18eda-13fc-4771-8ce0-11574a4469ad", "name": "Return Podcast Feed to Webhook", "type": "n8n-nodes-base.respondToWebhook", "position": [ 200, 460 ], "parameters": { "options": { "responseHeaders": { "entries": [ { "name": "Content-Type", "value": "application/xml" } ] } }, "respondWith": "text", "responseBody": "={{ $json.rssFeed }}" }, "typeVersion": 1.1 }, { "id": "d3afe3f0-79e4-48c1-a0d6-356b462156c7", "name": "Sticky Note5", "type": "n8n-nodes-base.stickyNote", "position": [ 260, -400 ], "parameters": { "color": 6, "width": 500, "height": 540, "content": "## Prepare Relevant Data\nConsolidates and formats data for Google Sheets storage." }, "typeVersion": 1 }, { "id": "f77ff10c-e4e3-4761-b4db-4c42d5831f5c", "name": "Manually Enter Other Data for Podcast Feed", "type": "n8n-nodes-base.set", "position": [ -460, 460 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "05d1c4f7-ebe7-4df8-925b-0e0d5539f172", "name": "baseUrl", "type": "string", "value": "https://n8n.io" }, { "id": "e8c6845e-887f-49e9-8336-ca2cb2a2fd29", "name": "podcastTitle", "type": "string", "value": "My Notes to Podcast" }, { "id": "bf2948ed-cffa-4d3f-9bab-5fb008d83b4c", "name": "podcastDescription", "type": "string", "value": "My Notes Read Aloud" }, { "id": "f5008697-3e52-4ae2-94da-c059b60a6de9", "name": "authorName", "type": "string", "value": "Your Name" }, { "id": "6595bf45-e054-4e18-ade9-13e38e6efedb", "name": "ownerName", "type": "string", "value": "Owner Name" }, { "id": "b21efe1c-e5b5-4bb3-bf07-a52859c7a607", "name": "ownerEmail", "type": "string", "value": "owner@email.com" }, { "id": "3f0b090c-0b5e-41cb-9841-05b7b8f83126", "name": "coverImageUrl", "type": "string", "value": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRPDcMnpgGkzIFxDpDaHEIFVg_D6nVG5Z0pPA&s" }, { "id": "1fb27792-1f2b-4a9a-a353-a64e31bb4747", "name": "language", "type": "string", "value": "en-us" }, { "id": "7c3d868a-f3c0-4fd0-8909-e4172f8a4b18", "name": "explicitContent", "type": "string", "value": "false" }, { "id": "6aa041b4-554c-4540-889c-e37a314d5842", "name": "itunesCategory", "type": "string", "value": "Technology" } ] }, "includeOtherFields": true }, "typeVersion": 3.4 } ], "active": true, "pinData": {}, "settings": { "executionOrder": "v1" }, "versionId": "4eb1c404-4e77-45ea-b413-4b79d8f40b1d", "connections": { "Merge": { "main": [ [ { "node": "Aggregate", "type": "main", "index": 0 } ] ] }, "OpenAI": { "main": [ [ { "node": "Merge", "type": "main", "index": 1 } ] ] }, "OpenAI1": { "main": [ [ { "node": "Give Audio Unique Name", "type": "main", "index": 0 } ] ] }, "Aggregate": { "main": [ [ { "node": "Rename Fields", "type": "main", "index": 0 } ] ] }, "Rename Fields": { "main": [ [ { "node": "Append Item to Google Sheet", "type": "main", "index": 0 } ] ] }, "Write RSS Feed": { "main": [ [ { "node": "Return Podcast Feed to Webhook", "type": "main", "index": 0 } ] ] }, "Webhook GET Note": { "main": [ [ { "node": "OpenAI1", "type": "main", "index": 0 }, { "node": "OpenAI", "type": "main", "index": 0 } ] ] }, "Give Audio Unique Name": { "main": [ [ { "node": "Upload Audio to Cloudinary", "type": "main", "index": 0 }, { "node": "Send Audio to Obsidian", "type": "main", "index": 0 } ] ] }, "Webhook GET Podcast Feed": { "main": [ [ { "node": "Get Items from Google Sheets", "type": "main", "index": 0 } ] ] }, "Upload Audio to Cloudinary": { "main": [ [ { "node": "Merge", "type": "main", "index": 0 } ] ] }, "Append Item to Google Sheet": { "main": [ [] ] }, "Get Items from Google Sheets": { "main": [ [ { "node": "Manually Enter Other Data for Podcast Feed", "type": "main", "index": 0 } ] ] }, "Manually Enter Other Data for Podcast Feed": { "main": [ [ { "node": "Write RSS Feed", "type": "main", "index": 0 } ] ] } } }