├── LICENSE ├── README.md ├── itunesheader.txt ├── itunesitems.txt └── podcast_live_template.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Gordon Haff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # podcast-python 2 | Script for automating the uploading of a new podcast 3 | 4 | This script: 5 | 6 | 1. Gets information such as duration from MP3 file 7 | 2. Allows user to input additional information (title, etc.) 8 | 3. Updates iTunes XML podcast file 9 | 4. Concatenates MP3 file with intro and outro segments 10 | 5. Creates OGG file version 11 | 6. Uploads XML and MP3 and OGG files to Amazon S3 and makes PUBLIC 12 | 13 | Requires: 14 | 15 | - boto https://github.com/boto/boto.git 16 | - mpeg1audio https://github.com/Ciantic/mpeg1audio/ 17 | - pydub (which requires ffmpeg to be installed) https://github.com/jiaaro/pydub 18 | - Edited MP3 file 19 | - MP3 intro and outro segments 20 | - Image file public on S3 21 | - header XML in header txt file 22 | - text file to store individual podcast XML, even if initially (mostly) empty. 23 | - Needs lines: 24 | - 25 | - 26 | - An existing RSS podcast feed XML file (or a null file) 27 | - Existing AWS S3 bucket and credentials 28 | - Supporting files in same directory as MP3 file 29 | - set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables 30 | - Redefine the global filename variables for your needs 31 | - Redefine the bucket name for your needs 32 | 33 | 34 | -------------------------------------------------------------------------------- /itunesheader.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | YOUR PODCAST TITLE 7 | http://www.MYWEBSITE.com 8 | en-us 9 | YOUR COPYRIGHT INFO 10 | THE LIFE AND TIMES OF DUNG BEETLES 11 | YOUR NAME 12 | A LONGER SUMMARY THAT DESCRIBES YOUR PODCAST 13 | Industry experts talk cloud computing, DevOps, IoT, containers, and more. 14 | 15 | YOUR NAME 16 | YOUR EMAIL 17 | 18 | 19 | 20 | no 21 | 22 | -------------------------------------------------------------------------------- /itunesitems.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /podcast_live_template.py: -------------------------------------------------------------------------------- 1 | #################### 2 | # Podcast post-production 3 | #!/usr/bin/python 4 | 5 | # Gordon Haff 6 | # 7 | # Requires: 8 | # boto https://github.com/boto/boto.git 9 | # mpeg1audio https://github.com/Ciantic/mpeg1audio/ 10 | # pydub (which requires ffmpeg to be installed) https://github.com/jiaaro/pydub 11 | # Edited MP3 file 12 | # MP3 intro and outro segments 13 | # Image file public on S3 14 | # header XML in header txt file 15 | # text file to store individual podcast XML, even if initially (mostly) empty. 16 | # Needs lines: 17 | # 18 | # 19 | # An existing RSS podcast feed XML file (or a null file) 20 | # Existing AWS S3 bucket and credentials 21 | # Supporting files in same directory as MP3 file 22 | # 23 | # set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables 24 | # Redefine the global filename variables for your needs 25 | # Redefine the bucket name for your needs 26 | # 27 | # This script: 28 | # 1. Gets information such as duration from MP3 file 29 | # 2. Allows user to input additional information (title, etc.) 30 | # 3. Updates iTunes XML podcast file 31 | # 4. Concatenates MP3 file with intro and outro segments 32 | # 5. Creates OGG file version 33 | # 6. Uploads XML and MP3 and OGG files to Amazon S3 and makes PUBLIC 34 | # 35 | 36 | from Tkinter import * 37 | from pydub import AudioSegment 38 | import tkFileDialog 39 | import os 40 | import boto 41 | from boto.s3.key import Key 42 | from os import path 43 | import time 44 | import mpeg1audio 45 | from gdata import service 46 | import gdata 47 | import atom 48 | import shutil 49 | 50 | ##################################################### 51 | # Define this stuff 52 | # set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables 53 | bucket_name='MYPODCASTBUCKET' 54 | ############################################## 55 | 56 | def open_file_dialog(): 57 | 58 | global filename 59 | global oggexists 60 | global Filelength 61 | global FilelengthStr 62 | global DurationStr 63 | global OggFilename 64 | global iTunesFile 65 | global iTunesHeader 66 | global iTunesItems 67 | global theDirname 68 | 69 | # Change names as desired 70 | leadIn = "podcastintro.mp3" #intro to prepend 71 | leadOut = "podcastoutro.mp3" #outro to append 72 | iTunesFile = "itunesrss.xml" #your podcast feed 73 | iTunesHeader = "itunesheader.txt" #the header on your podcast feed file 74 | iTunesItems = "itunesitems.txt" #you'll need to populate with XML for a podcast 75 | 76 | filename = tkFileDialog.askopenfilename(filetypes=[("MP3 files",".mp3")]) 77 | v.set(filename) 78 | 79 | FileBase, FileExtension = path.splitext(filename) 80 | theDirname = path.dirname(filename) 81 | 82 | renameOriginal = FileBase + "_original" + FileExtension 83 | shutil.copy2(filename,renameOriginal) 84 | 85 | # add path to the various locations 86 | leadIn = path.join(theDirname,leadIn) 87 | leadOut = path.join(theDirname,leadOut) 88 | iTunesFile = path.join(theDirname,iTunesFile) 89 | iTunesHeader = path.join(theDirname,iTunesHeader) 90 | iTunesItems = path.join(theDirname,iTunesItems) 91 | 92 | # Concatenate MP3 file with header and footer 93 | baseSegment = AudioSegment.from_mp3(filename) 94 | introSegment = AudioSegment.from_mp3(leadIn) 95 | outroSegment = AudioSegment.from_mp3(leadOut) 96 | 97 | completeSegment = introSegment + baseSegment + outroSegment 98 | 99 | #export new MP3 file and also an ogg version 100 | completeSegment.export(filename,"mp3") 101 | OggFilename = FileBase + '.ogg' 102 | completeSegment.export(OggFilename,"ogg") 103 | 104 | # Error checking from prior version. "Shouldn't" be possible to happen now 105 | oggexists = True 106 | if not path.isfile(OggFilename): 107 | oggexists = False 108 | StatusText.set("Status: OGG file missing") 109 | 110 | Filelength = path.getsize(filename) 111 | FilelengthStr.set("Filelength (bytes): " + str(Filelength)) 112 | 113 | timestruc = time.gmtime(path.getmtime(filename)) 114 | 115 | TimestampEntry.delete(0,END) 116 | TimestampEntry.insert(0,time.strftime("%a, %d %b %G %T",timestruc) + " GMT") 117 | 118 | mp3 = mpeg1audio.MPEGAudio(filename) 119 | DurationStr = str(mp3.duration) 120 | DurationLabelStr.set("Duration: " + DurationStr) 121 | 122 | def do_stuff(): 123 | 124 | createXML() 125 | uploadtoAMZN() 126 | StatusText.set("Status: Success (AFAIK)") 127 | 128 | def createXML(): 129 | 130 | global MP3url 131 | 132 | # create an XML file containing contents for new for iTunes 133 | FileBase, FileExtension = path.splitext(filename) 134 | XMLfilename = FileBase + '.xml' 135 | MP3url = "http://s3.amazonaws.com/"+bucket_name+"/"+path.basename(filename) 136 | inp = file(XMLfilename, 'w') 137 | 138 | inp.write("\n") 139 | inp.write(""+PodcastTitleEntry.get()+"\n") 140 | inp.write(""+PodcastSubtitleEntry.get()+"\n") 141 | inp.write(""+PodcastSummaryText.get(1.0,END)+"\n") 142 | inp.write("\n") 143 | inp.write(""+MP3url+"\n") 144 | inp.write(""+TimestampEntry.get()+"\n") 145 | inp.write(""+DurationStr+"\n") 146 | inp.write("cloud\n") 147 | inp.write("no\n") 148 | inp.write("") 149 | inp.write("") 150 | 151 | inp.close() 152 | 153 | #Now concatenate to make a new itunesxml.xml file 154 | 155 | #create backup of existing iTunes XML file in case something goes kaka 156 | iTunesBackup = path.join(theDirname,"itunesxmlbackup.xml") 157 | shutil.copy2(iTunesFile,iTunesBackup) 158 | 159 | #create temporary iTunes item list (to overwrite the old one later on) 160 | outfile = file("iTunestemp.xml", 'w') 161 | 162 | # create a new items file 163 | with open(XMLfilename) as f: 164 | for line in f: 165 | outfile.write(line) 166 | with open(iTunesItems) as f: 167 | for line in f: 168 | outfile.write(line) 169 | outfile.close() 170 | 171 | #replace the old items file with the new one 172 | shutil.copy2("iTunestemp.xml",iTunesItems) 173 | 174 | #now we're ready to create the new iTunes File 175 | outfile = file(iTunesFile, 'w') 176 | 177 | # create a new items file 178 | with open(iTunesHeader) as f: 179 | for line in f: 180 | outfile.write(line) 181 | with open(iTunesItems) as f: 182 | for line in f: 183 | outfile.write(line) 184 | outfile.close() 185 | 186 | def uploadtoAMZN(): 187 | 188 | # Upload files to Amazon S3 189 | # Change 'public-read' to 'private' if you want to manually set ACLs 190 | conn = boto.connect_s3() 191 | bucket = conn.get_bucket(bucket_name) 192 | k = Key(bucket) 193 | k.key = path.basename(filename) 194 | k.set_contents_from_filename(filename) 195 | k.set_canned_acl('public-read') 196 | 197 | if oggexists: 198 | k.key = path.basename(OggFilename) 199 | k.set_contents_from_filename(OggFilename) 200 | k.set_canned_acl('public-read') 201 | 202 | k.key = path.basename(iTunesFile) 203 | k.set_contents_from_filename(iTunesFile) 204 | k.set_canned_acl('public-read') 205 | 206 | ##################################################### 207 | 208 | root = Tk() 209 | 210 | Label(root,text="Podcast Title:").grid(row=1, sticky=W) 211 | 212 | PodcastTitleEntry = Entry(root, width=80, borderwidth=1) 213 | PodcastTitleEntry.grid(row=2, sticky=W) 214 | 215 | Label(root,text="iTunes subtitle:").grid(row=3, sticky=W) 216 | 217 | PodcastSubtitleEntry=Entry(root, width=80, borderwidth=1) 218 | PodcastSubtitleEntry.grid(row=4, sticky=W) 219 | 220 | Label(root,text="iTunes summary:").grid(row=5, sticky=W) 221 | 222 | PodcastSummaryText=Text(root,width=80,height=4,borderwidth=2) 223 | PodcastSummaryText.grid(row=6,sticky=W) 224 | 225 | 226 | Button(root, text='Select file...',command=open_file_dialog).grid(row=9, column=0, sticky=W) 227 | 228 | v = StringVar() 229 | Label(root, textvariable=v,justify=LEFT,fg="blue").grid(row=10,sticky=W) 230 | 231 | TimestampEntry = Entry(root,width=50,borderwidth=1) 232 | TimestampEntry.grid(row=11,sticky=W) 233 | TimestampEntry.insert(END,"Time/date (default filled in automatically from file)") 234 | 235 | FilelengthStr = StringVar() 236 | FilelengthStr.set("Filelength (bytes):") 237 | 238 | FilelengthLabel = Label(root,textvariable=FilelengthStr) 239 | FilelengthLabel.grid(row=12,sticky=W) 240 | 241 | DurationLabelStr = StringVar() 242 | DurationLabelStr.set("Duration: "); 243 | DurationLabel = Label(root,textvariable=DurationLabelStr) 244 | DurationLabel.grid(row=13,sticky=W) 245 | 246 | Button(root, text='Go!',command=do_stuff).grid(row=14, sticky=W) 247 | 248 | StatusText = StringVar() 249 | StatusText.set("Status: Nothing to report") 250 | 251 | StatusLabel=Label(root,textvariable=StatusText) 252 | StatusLabel.grid(row=15, sticky=W) 253 | 254 | root.mainloop() 255 | 256 | 257 | 258 | --------------------------------------------------------------------------------