Skip to content

Instantly share code, notes, and snippets.

@sagebind
Created March 12, 2025 22:46
Show Gist options
  • Save sagebind/bc0e054279b7af2eaaf556909539dfe1 to your computer and use it in GitHub Desktop.
Save sagebind/bc0e054279b7af2eaaf556909539dfe1 to your computer and use it in GitHub Desktop.
N8n workflow for converting YouTube channels or playlists into podcasts
{
"name": "Convert YouTube to podcast",
"nodes": [
{
"parameters": {
"path": "/youtube/channel/:channel/feed.rss",
"responseMode": "responseNode",
"options": {}
},
"id": "8ba9b21e-ea9e-4ae3-8b68-d479944e39d4",
"name": "Request channel feed",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
620,
320
],
"webhookId": "b6638375-24f1-4835-8011-fbb400bcc9c9"
},
{
"parameters": {
"url": "=https://www.youtube.com/feeds/videos.xml?channel_id={{ $json.params.channel }}",
"options": {}
},
"id": "ccca9d86-d527-4aad-b935-389f91f02637",
"name": "Fetch channel feed",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
840,
320
]
},
{
"parameters": {
"url": "=https://www.youtube.com/feeds/videos.xml?playlist_id={{ $json.params.playlist }}",
"options": {}
},
"id": "ab51edb5-7aae-4cbc-a431-46e7f71361c3",
"name": "Fetch playlist feed",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
840,
120
]
},
{
"parameters": {
"path": "/youtube/playlist/:playlist/feed.rss",
"responseMode": "responseNode",
"options": {}
},
"id": "06e40cc9-9eb7-4355-852a-01949c299d4b",
"name": "Request playlist feed",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
620,
120
],
"webhookId": "fde5d4ab-9a21-4310-a56c-84befc8b51fb"
},
{
"parameters": {
"url": "={{ $json.feed.author.uri }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1280,
220
],
"id": "75e37501-709c-4c1e-b523-16fa75150219",
"name": "Fetch channel info"
},
{
"parameters": {
"operation": "extractHtmlContent",
"extractionValues": {
"values": [
{
"key": "image",
"cssSelector": "meta[property=\"og:image\"]",
"returnValue": "attribute",
"attribute": "content"
},
{
"key": "description",
"cssSelector": "meta[property=\"og:description\"]",
"returnValue": "attribute",
"attribute": "content"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.html",
"typeVersion": 1.2,
"position": [
1500,
220
],
"id": "a556d6a9-66e0-4e2d-b924-9f148ccca851",
"name": "Extract channel info"
},
{
"parameters": {
"mode": "jsonToxml",
"dataPropertyName": "rss",
"options": {
"attrkey": "__attr__"
}
},
"id": "82c1dd84-de37-476f-98ed-74857ecb4d84",
"name": "JSON to RSS",
"type": "n8n-nodes-base.xml",
"typeVersion": 1,
"position": [
1940,
220
]
},
{
"parameters": {
"options": {
"attrkey": "__attr__",
"mergeAttrs": false
}
},
"id": "d61d745c-12ee-403f-9857-06723df81ef5",
"name": "Parse feed",
"type": "n8n-nodes-base.xml",
"typeVersion": 1,
"position": [
1060,
220
]
},
{
"parameters": {
"jsCode": "let feed = $('Parse feed').first().json.feed;\nlet channelInfo = $('Extract channel info').first().json;\nlet webhookUrl = $('Request playlist feed').isExecuted ?\n $('Request playlist feed').first().json.webhookUrl :\n $('Request channel feed').first().json.webhookUrl;\n\nlet webhookBaseUrl = webhookUrl.replace(/(\\/webhook(-test)?)\\/.+/, \"$1\");\nlet actualWebhookUrl = webhookUrl\n .replace(\":playlist\", feed['yt:playlistId'])\n .replace(\":channel\", feed['yt:channelId']);\n\nlet rss = {\n __attr__: {\n version: \"2.0\",\n \"xmlns:itunes\": \"http://www.itunes.com/dtds/podcast-1.0.dtd\",\n \"xmlns:podcast\": \"https://podcastindex.org/namespace/1.0\",\n \"xmlns:atom\": \"http://www.w3.org/2005/Atom\",\n \"xmlns:content\": \"http://purl.org/rss/1.0/modules/content/\"\n },\n channel: {\n title: feed.title,\n description: channelInfo.description,\n author: feed.author,\n link: feed.author.uri,\n \"atom:link\": {\n __attr__: {\n href: actualWebhookUrl,\n rel: \"self\",\n type: \"application/rss+xml\"\n }\n },\n language: \"en\",\n \"itunes:author\": feed.author.name,\n \"itunes:type\": \"episodic\",\n \"itunes:explicit\": \"No\",\n \"itunes:image\": {\n __attr__: {\n href: channelInfo.image\n }\n },\n item: feed.entry.map(entry => ({\n guid: {\n __attr__: {\n isPermaLink: false\n },\n _: entry.id\n },\n title: entry.title,\n description: entry['media:group']['media:description'],\n pubDate: entry.published,\n link: entry.link.__attr__.href,\n enclosure: {\n __attr__: {\n url: `${webhookBaseUrl}/824dcae0-76b7-4157-8eb8-4baedc17600f/youtube/video/${entry['yt:videoId']}/audio.mp3`,\n length: 100000000,\n type: \"audio/mpeg\"\n }\n },\n \"itunes:episodeType\": \"full\",\n \"itunes:explicit\": \"No\",\n \"itunes:image\": {\n __attr__: {\n href: entry['media:group']['media:thumbnail'].__attr__.url\n }\n }\n }))\n }\n};\n\nreturn [{ rss }];"
},
"id": "b1ce4807-091c-4e2a-b060-7b084e299f0e",
"name": "Generate RSS",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1720,
220
]
},
{
"parameters": {
"path": "/youtube/video/:videoId/audio.mp3",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
620,
580
],
"id": "24d88958-6842-4df1-a4e5-b5f9d305bfbb",
"name": "Request episode audio",
"webhookId": "824dcae0-76b7-4157-8eb8-4baedc17600f"
},
{
"parameters": {
"command": "if [ ! -f /data/bin/ffmpeg/ffmpeg ]; then\n mkdir -p /data/bin\n cd /data/bin\n wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz\n tar xzfJ *.tar.xz\n rm *.tar.xz\n mv ffmpeg-*-amd64-static ffmpeg\nfi"
},
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
840,
840
],
"id": "064cda60-049c-43ff-b71d-8474a0241db1",
"name": "Install FFmpeg"
},
{
"parameters": {
"command": "=if [ ! -f /data/youtube-audio/{{ $json.params.videoId }}.mp3 ]; then\n mkdir -p /data/youtube-audio\n cd /data/youtube-audio\n /data/bin/yt-dlp --ffmpeg-location /data/bin/ffmpeg -o '%(id)s.%(ext)s' -f bestaudio --quiet --extract-audio --audio-format mp3 -- \"https://www.youtube.com/watch?v={{ $json.params.videoId }}\"\nfi"
},
"id": "2b6ef338-3405-4611-b469-d4ee17c96c1c",
"name": "Download audio",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
840,
580
]
},
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
620,
840
],
"id": "713dd1e5-0e6e-4608-a412-f32f2c784283",
"name": "Install dependencies"
},
{
"parameters": {
"fileSelector": "=/data/youtube-audio/{{ $('Request episode audio').item.json.params.videoId }}.mp3",
"options": {}
},
"type": "n8n-nodes-base.readWriteFile",
"typeVersion": 1,
"position": [
1060,
580
],
"id": "8659a502-de5f-43af-b8ef-7bee9023d944",
"name": "Load audio file",
"alwaysOutputData": false
},
{
"parameters": {
"command": "if [ -f /data/bin/yt-dlp ]; then\n /data/bin/yt-dlp -U\nelse\n mkdir -p /data/bin\n wget -O /data/bin/yt-dlp \"https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux\"\n chmod +x /data/bin/yt-dlp\nfi"
},
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
1060,
940
],
"id": "a0017158-44a7-4fab-8065-eb631be5ca21",
"name": "Update yt-dlp"
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "weeks"
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
840,
1040
],
"id": "2fc098b4-697d-42c5-a772-756dcc7b38ed",
"name": "Update yt-dlp on a schedule"
},
{
"parameters": {
"respondWith": "text",
"responseBody": "={{ $json.rss }}",
"options": {
"responseHeaders": {
"entries": [
{
"name": "content-type",
"value": "application/rss+xml"
},
{
"name": "cache-control",
"value": "public, max-age=3600"
}
]
}
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
2160,
220
],
"id": "c1d126b2-0069-4432-9ed6-c29d304ffee3",
"name": "Return RSS feed"
},
{
"parameters": {
"respondWith": "binary",
"options": {
"responseHeaders": {
"entries": [
{
"name": "cache-control",
"value": "public, max-age=86400"
}
]
}
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1280,
580
],
"id": "1782d436-47d6-4a8f-9860-9c5dde550f67",
"name": "Return audio file"
}
],
"pinData": {},
"connections": {
"Fetch channel feed": {
"main": [
[
{
"node": "Parse feed",
"type": "main",
"index": 0
}
]
]
},
"Request channel feed": {
"main": [
[
{
"node": "Fetch channel feed",
"type": "main",
"index": 0
}
]
]
},
"Request playlist feed": {
"main": [
[
{
"node": "Fetch playlist feed",
"type": "main",
"index": 0
}
]
]
},
"Fetch playlist feed": {
"main": [
[
{
"node": "Parse feed",
"type": "main",
"index": 0
}
]
]
},
"Fetch channel info": {
"main": [
[
{
"node": "Extract channel info",
"type": "main",
"index": 0
}
]
]
},
"Extract channel info": {
"main": [
[
{
"node": "Generate RSS",
"type": "main",
"index": 0
}
]
]
},
"JSON to RSS": {
"main": [
[
{
"node": "Return RSS feed",
"type": "main",
"index": 0
}
]
]
},
"Parse feed": {
"main": [
[
{
"node": "Fetch channel info",
"type": "main",
"index": 0
}
]
]
},
"Generate RSS": {
"main": [
[
{
"node": "JSON to RSS",
"type": "main",
"index": 0
}
]
]
},
"Request episode audio": {
"main": [
[
{
"node": "Download audio",
"type": "main",
"index": 0
}
]
]
},
"Download audio": {
"main": [
[
{
"node": "Load audio file",
"type": "main",
"index": 0
}
]
]
},
"Install FFmpeg": {
"main": [
[
{
"node": "Update yt-dlp",
"type": "main",
"index": 0
}
]
]
},
"Install dependencies": {
"main": [
[
{
"node": "Install FFmpeg",
"type": "main",
"index": 0
}
]
]
},
"Load audio file": {
"main": [
[
{
"node": "Return audio file",
"type": "main",
"index": 0
}
]
]
},
"Update yt-dlp on a schedule": {
"main": [
[
{
"node": "Update yt-dlp",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "836af500-30ee-401a-b8ef-30903abb536f",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "ad45f341659c6e3c759843b4eeb160f3184915669c1330680fb1c5e3d601e663"
},
"id": "4MFLuR9xEMJ7wZU4",
"tags": []
}
@sagebind
Copy link
Author

Please note that this workflow will download binaries for FFmpeg and yt-dlp from the Internet. This isn't the safest or most secure approach. You may want to modify this to use a system-wide installation of FFmpeg and yt-dlp which is managed outside of this workflow. If you're running n8n in Docker, that means you'll probably need to build a custom Docker image.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment