Last active 1569718003

Generates an iTunes compatible podcast from a Youtube playlist, storing the audio files in a local directory. Could be adapted to store the files in Dropbox or on another media provider if desired | Requires python 2.7, requests, wget, and youtube-dl | Configuration at top of file

Revision 95a9392cab5968adf3f10c0ef4549b828b031948

ytPlaylistToPodcast.py Raw
1#!/usr/bin/env python2
2# This file is released as public domain by Steven Smith (blha303) in Apr 2015
3# In areas where public domain isn't a thing, I release it under the MIT license.
4# Although credit would be nice if you use this in something cool. And email me a link too pls.
5import time,os,requests,json,subprocess
6from urllib import urlretrieve
7
8DTFORMAT = "%a, %b %d %Y %H:%M:%S +0000" # Do not modify, needs to be at top
9
10playlisturl = "https://www.youtube.com/playlist?list=UU9CuvdOVfMPvKCiwdGKL3cQ"
11# generate your key via google's api dashboard. needs to have access to youtube's data api v3
12apikey = "AIzaSyAuwEEcpOGG230tm7na1KdO0tHFm2S_dIE"
13webroot = "http://domain.bla"
14webpath = "/var/www"
15outpdir = "/podcastdir"
16outpfn = "/{id}.m4a"
17xmlfn = outpdir + "/podcast.xml"
18
19podcast = dict(
20 self = webroot + xmlfn, # should point to xml
21 title = "A cool podcast",
22 link = "http://blha303.com.au",
23 description = "DAE podcast?",
24 copyright = "Copyright 2015 Youtube",
25 now = time.strftime(DTFORMAT),
26 language = "en-us",
27 subtitle = "Youtube is pretty cool, ey",
28 author = "Me",
29 summary = "Wip wap wop",
30 owner_name = "Me",
31 owner_email = "me@you.us",
32 image = webroot + outpdir + "/podcast.png",
33 category = "yay",
34 explicit = "yes" # or no
35)
36
37item_info = dict(
38 author = "Me",
39 summary = "Just more info",
40 category = "Blablabla",
41 keywords = "autogen"
42)
43
44BASE = u"""<?xml version="1.0" encoding="utf-8"?>
45 <rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
46 <channel>
47 <atom:link href="{self}" rel="self" type="application/rss+xml" />
48 <title>{title}</title>
49 <link>{link}</link>
50 <description>{description}</description>
51 <lastBuildDate>{now}</lastBuildDate>
52 <language>{language}</language>
53 <copyright>{copyright}</copyright>
54 <itunes:subtitle>{subtitle}</itunes:subtitle>
55 <itunes:author>{author}</itunes:author>
56 <itunes:summary>{summary}</itunes:summary>
57 <itunes:owner>
58 <itunes:name>{owner_name}</itunes:name>
59 <itunes:email>{owner_email}</itunes:email>
60 </itunes:owner>
61 <itunes:image href="{image}" />
62 <itunes:category text="{category}" />
63 """
64BASE2 = u"""<itunes:explicit>{explicit}</itunes:explicit>
65 </channel>
66 </rss>
67"""
68ITEM = u"""<item>
69 <title>{fulltitle}</title>
70 <link>https://www.youtube.com/watch?v={id}</link>
71 <itunes:author>{author}</itunes:author>
72 <description>{description}</description>
73 <itunes:summary>{summary}</itunes:summary>
74 <enclosure url="{lurl}" length="{size}" type="video/mp4"/>
75 <guid>{lurl}</guid>
76 <pubDate>{upload_date}</pubDate>
77 <itunes:order>{order}</itunes:order>
78 <itunes:duration>{duration}</itunes:duration>
79 <itunes:keywords>{keywords}</itunes:keywords>
80 <category>{category}</category>
81 <itunes:explicit>{explicit}</itunes:explicit>
82 </item>
83 """
84
85def get_time(id):
86 data = requests.get("https://www.googleapis.com/youtube/v3/videos", params={'id': id, 'part': "snippet,statistics,recordingDetails", "key": apikey}).json()
87 return time.strftime(DTFORMAT, time.strptime(data["items"][0]["snippet"]["publishedAt"], "%Y-%m-%dT%H:%M:%S.000Z"))
88
89def download_file(item):
90 fn = webpath + outpdir + outpfn.format(**item)
91 if not os.path.isfile(fn):
92 print " Downloading"
93 url = [i for i in item["formats"] if i["format_id"] == "nondash-140"][0]["url"]
94 print "".join(subprocess.check_output(["wget", url, "-O", fn]).splitlines()[-5])
95 return fn
96 else:
97 print " File exists"
98 return fn
99
100def get_output(items):
101 outp = []
102 items = sorted(items, key=lambda k: k["upload_date"], reverse=True)
103 for x,item in enumerate(items):
104 item.update(item_info)
105 print "Processing {fulltitle} ({id})".format(**item)
106 m,s = divmod(item["duration"], 60)
107 h,m = divmod(m, 60)
108 item["order"] = x+1
109 item["description"] = item["description"].replace(u"\u25ba", u"&gt;")
110 item["duration"] = u"%d:%02d:%02d" % (h,m,s)
111 item["upload_date"] = get_time(item["id"])
112 item["size"] = str(os.path.getsize(download_file(item)))
113 item["explicit"] = podcast["explicit"]
114 item["lurl"] = webroot + outpdir + outpfn.format(**item)
115 outp.append(ITEM.format(**item))
116 print " Processed"
117 print "Process complete"
118 return BASE.format(**podcast) + "".join(outp) + BASE2.format(**podcast)
119
120if __name__ == "__main__":
121 print "Getting playlist data from youtube, this can take a while if the playlist is large..."
122 data = subprocess.check_output(['youtube-dl', playlisturl, '--playlist-end', '30', '--match-filter', 'duration > 300', '-f', '140', '-j']).splitlines()
123 print "Playlist data obtained, starting processing..."
124 with open(webpath + xmlfn, "w") as f:
125 f.write(get_output(json.loads(u"[" + u",".join(data) + u"]")).encode('ascii', 'ignore'))
126