Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
automatically hosting a podcast in your Dropbox public folder
#!/usr/bin/env ruby -wKU
#
# by Kelan Champagne http://yeahrightkeller.com
# with edits by sjschultze
# and advanced metadata handling by lukf
#
# A script to generate a personal podcast feed, hosted on Dropbox
#
# Inspired by http://hints.macworld.com/article.php?story=20100421153627718
#
# Simply put this, and some .mp3 or .m4a files in a sub-dir under your Dropbox
# Public folder, create your config.txt file in the same directory, and run the script. To get
# the public_url_base value, you can right click on a file in that folder
# in Finder, then go to Dropbox > Copy Public Link, and then remove the
# filename.
# iTunes recommends artwork in the JPEG or PNG file formats and in the RGB color space
# with a minimum size of 1400 x 1400 pixels and a maximum size of 2048 x 2048 pixels.
# You'll need a *direct* link to the image, hosted wherever you want. I use cl.ly.
#
# The following lines are a template for your config.txt file; just remove the hashes:
# podcast_title = The Adventures Of Harry Lime
# podcast_description = Orson Welles' radio drama, between 1951 and 1952
# podcast_artwork = http://cl.ly/image/2x3y3A2l1P2S/01.%20Too%20Many%20Crooks.jpg
# public_url_base = https://dl.dropboxusercontent.com/u/55322715/Audio/The%20Adventures%20Of%20Harry%20Lime
#
# Todo:
# * Parse items' source date, instead of passing through
# * Do even more sanitising of the metadata.
#
# Notes:
# * You'll need to re-run it after adding new files to the dir, or you can
# set up Folder Actions as suggested by the above hint (sample AppleScript
# in comments at the bottom of this file).
# * This script uses `ffprobe` to get the source media metadata.
# You'll need to have the binary installed (in /usr/bin on OS X).
require 'date'
require 'erb'
include ERB::Util
# Set up user variables
podcast_title = ""
podcast_description = ""
podcast_artwork = ""
public_url_base = ""
# Import configuration data
podcast_infos = IO.readlines("config.txt")
podcast_infos.select {|i|i.start_with?('#') == false} # Ignore comment lines in input
podcast_infos.each {|i|
id = i.split("=")[0].gsub(' ', '')
case id
when "podcast_title"
podcast_title = i.split("=")[1].chomp
when "podcast_description"
podcast_description = i.split("=")[1].chomp
when "podcast_artwork"
podcast_artwork = i.split("=")[1].chomp
when "public_url_base"
public_url_base = i.split("=")[1].chomp
else
puts "Unrecognised config data: " + i
end
}
# Generated values
date_format = '%a, %d %b %Y %H:%M:%S %z'
podcast_pub_date = DateTime.now.strftime(date_format)
# Build the items
items_content = ""
Dir.entries('.').each do |file|
next if file =~ /^\./ # ignore invisible files
next unless file =~ /\.(mp3|m4a)$/ # only use audio files
puts "adding file: #{file}"
# Aquiring source metadata
item_filename = File.basename(file, '').split('.')[0]
item_title_number = `ffprobe 2> /dev/null -show_format "#{file}" | grep TAG:track= | cut -d '=' -f 2`.sub(/^.*? = "/, '').sub(/"$/, '').chomp.to_s
item_title_source = `ffprobe 2> /dev/null -show_format "#{file}" | grep TAG:title= | cut -d '=' -f 2`.sub(/^.*? = "/, '').sub(/"$/, '').chomp.to_s
item_text_artist = `ffprobe 2> /dev/null -show_format "#{file}" | grep TAG:artist= | cut -d '=' -f 2`.sub(/^.*? = "/, '').sub(/"$/, '').chomp.to_s
item_text_albumartist = `ffprobe 2> /dev/null -show_format "#{file}" | grep TAG:albumartist= | cut -d '=' -f 2`.sub(/^.*? = "/, '').sub(/"$/, '').chomp.to_s
item_text_description = `ffprobe 2> /dev/null -show_format "#{file}" | grep TAG:description= | cut -d '=' -f 2`.sub(/^.*? = "/, '').sub(/"$/, '').chomp.to_s
item_text_synopsis = `ffprobe 2> /dev/null -show_format "#{file}" | grep TAG:synopsis= | cut -d '=' -f 2`.sub(/^.*? = "/, '').sub(/"$/, '').chomp.to_s # Also known as 'long description'
item_text_comment = `ffprobe 2> /dev/null -show_format "#{file}" | grep TAG:comment= | cut -d '=' -f 2`.sub(/^.*? = "/, '').sub(/"$/, '').chomp.to_s
item_duration_source = `ffprobe 2> /dev/null -show_format "#{file}" | grep duration_time= | cut -d '=' -f 2`.sub(/^.*? = "/, '').sub(/"$/, '').chomp.to_s
item_pub_date_source = `ffprobe 2> /dev/null -show_format "#{file}" | grep TAG:date= | cut -d '=' -f 2`.sub(/^.*? = "/, '').sub(/"$/, '').chomp.to_s
# Convert number to ordinal
if item_title_number != ""
item_title_number += ". "
end
# Get correct artist; defaulting to artist
if item_text_artist == ""
item_text_artist = item_text_albumartist
elsif item_text_albumartist.include? item_text_artist
item_text_artist = item_text_albumartist
end
# Figure out short text
item_text_short_array = [item_text_description, item_text_synopsis]
item_text_short = item_text_short_array.sort_by(&:length)[0].to_s
if item_text_short == ""
item_text_short = item_text_comment
if item_text_short == ""
item_text_short = item_text_artist
end
end
# Eliminate duplicates for long text
item_text_long_array = [item_text_artist, item_text_description, item_text_synopsis, item_text_comment]
item_text_long_array = item_text_long_array.select {|e|item_text_long_array.grep(Regexp.new(e)).size == 1}
# Make sure that no component of long text is nil
item_text_long_array.each { |snil| snil = snil.to_s }
# Combine long text and add line breaks
item_text_long = ""
item_text_long_array.each { |s| item_text_long += s + "\n"}
item_text_long = item_text_long.chomp()
# Figure out author
item_author = item_text_artist
if item_author == ""
item_author = item_text_albumartist
end
# Figure out title base
if item_title_source == ""
item_title_source = item_filename
end
# Set remaining metadata without logic
item_title = item_title_number + item_title_source
item_url = "#{public_url_base.gsub("https", "http")}/#{url_encode(file)}"
item_size_in_bytes = File.size(file).to_s
item_duration = item_duration_source
item_pub_date = item_pub_date_source
item_guid = item_url + url_encode(podcast_pub_date)
item_content = <<-HTML
<item>
<title>#{item_title}</title>
<description>#{item_text_long}</description>
<itunes:subtitle>#{item_text_short}</itunes:subtitle>
<itunes:summary>#{item_text_short}</itunes:summary>
<enclosure url="#{item_url}" length="#{item_size_in_bytes}" type="audio/mpeg" />
<category>Podcasts</category>
<pubDate>#{item_pub_date}</pubDate>
<guid>#{item_guid}</guid>
<itunes:author>#{item_author}</itunes:author>
<itunes:duration>#{item_duration}</itunes:duration>
</item>
HTML
items_content << item_content
end
# Build the whole file
content = <<-HTML
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
<channel>
<title>#{podcast_title}</title>
<description>#{podcast_description}</description>
<pubDate>#{podcast_pub_date}</pubDate>
<itunes:image href="#{podcast_artwork}"/>
<itunes:subtitle>#{podcast_description.to_s[0,254]}</itunes:subtitle>
<itunes:summary>#{podcast_description.to_s[0,3999]}</itunes:summary>
#{items_content}
</channel>
</rss>
HTML
# write it out
output_file = File.new("podcast.rss", 'w')
re = "[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]" # Regex for avoiding XML problems
content.gsub(re, "")
output_file.write(content)
output_file.close
# = Sample AppleScript to auto-run this script. =
# This AppleScript also touches the new file so that it's modification
# date (and thus the pubDate in the podcast) are the date/time that you
# put it in the folder.
#
# To install:
# - Open AppleScript Editor and copy-paste the below code (minus #'s)
# - Save the script to "/Library/Scripts/Folder Action Scripts"
# - Control-click the podcast folder, "Services > Folder Actions Setup"
# and choose your script
#
#on adding folder items to this_folder after receiving added_items
# set the_folder to POSIX path of this_folder
# set the_folder_quoted to (the quoted form of the_folder as string)
#
# repeat with this_item in added_items
# set the_item to POSIX path of this_item
# set the_item_quoted to (the quoted form of the_item as string)
# do shell script "touch " & the_item_quoted
# end repeat
#
# tell application "Finder"
# display dialog "cd " & the_folder_quoted & ";./generate_personal_podcast.rb"
# do shell script "cd " & the_folder_quoted & ";./generate_personal_podcast.rb"
# end tell
#
# end adding folder items to
@lukf

This comment has been minimized.

Copy link
Owner Author

commented Dec 31, 2014

I have multiple podcasts set up with this and in the past, it was a pain to update this script in all of them. To solve this problem, there won't be any user modification in this file anymore, but in a 'config.txt' file in the folder the script is in.

@lukf

This comment has been minimized.

Copy link
Owner Author

commented Dec 31, 2014

Metadata handling

  • The only required setup is public_url_base in config.txt.
  • The podcast's title, description and artwork URL are set up in the config.txt. The description is used in multiple tags and shortened accordingly for each.
  • An episode's title is extracted from the audio file using ffprobe. If that fails, the filename without extension is used. An ordinal number is added, if 'Track-#' is set for the audio file.
  • For further usage, artist data is needed. I try to be smart about using 'Album Artist', if it has superior information to 'Artist'.
  • The subtitle/ summary tags are primarily set from the source file's 'Description' and 'Long Description' (aka. 'Synopsis') tags. If both exist, the shorter is used. If both are empty, they're set to artist data.
  • Episode description information is a combination of artist, 'Description', 'Long Description', and 'Comment' information. I try to avoid duplicated text, if one tag contains the entirety of another.
  • An iTunes author tag is set using previously aquired artist information.
  • Episode GUIDs are derived from the file's URL and the podcast's pubDate.
  • An episode's duration is read using ffprobe.
  • Episode pubDate is sourced from the source audio tags, using ffprobe.
@mikeoertli

This comment has been minimized.

Copy link

commented Jun 19, 2015

if the file names have a period in them, the split command will not properly parse the title out, a hack of a fix it so change it to split on ".mp3" if you're in a pinch and need periods in the file name. Thank you for the fork though, this was exactly what I needed!

@lukf

This comment has been minimized.

Copy link
Owner Author

commented Aug 9, 2015

Splitting at ".mp3" would break support for other kinds of audio files. Let me know if you have another solution to mikeoertli's problem.

@lukf

This comment has been minimized.

Copy link
Owner Author

commented Aug 30, 2015

How to set it all up:

Notice: Terminal commands are in quotes that are not to be copied. If a path is presented outside the quotes, drop in that file to add its path as it is in your setup.

  • Create a new folder dedicated to your podcast in /Dropbox/Public/
  • in it, create the text file named "generate_personal_podcast.rb" with the whole content of this script
  • Make the file executable by running the Terminal command
    "chmod a+x " /path/generate_personal_podcast.rb
  • Create another file named "config.txt" and paste in these contents, replacing the values to describe your podcast:

podcast_title = The Adventures of Harry Lime
podcast_description = classic radio drama
public_url_base = https://dl.dropboxusercontent.com/u/55322715/Audio/The%20Adventures%20Of%20Harry%20Lime

Get the public_url_base by right-clicking config.txt in Finder and choosing Dropbox → Copy Public Link, removing /config.txt from the link.
You can also add artwork information, as described in the beginning of the script.

  • Put the audio files in the folder
  • Run the script by running the Terminal command
    "ruby " /path/generate_personal_podcast.rb
  • A new RSS feed file should appear. Copy its public URL, as described before and add it to your feed aggregator.
  • If the feed looks right and you would like to automate updating it, follow the instructions at the end of the script to set up Folder Actions.
@benutzerfreund

This comment has been minimized.

Copy link

commented Feb 16, 2016

lukf, thank you so much for this – exactly what I was looking for.

Unfortunately, I can't get it to work. There seems to be a problem with the paths at my machine (I have no experience with Ruby…). It starts with the location of the config.txt. I placed it in the same dir like the .rb, but in Terminal I get:

49: syntax error, unexpected tIDENTIFIER, expecting ')'
podcast_infos.select {|i|i.start_with?('#'...

When I hardcode the strings from the config file into the code, I get:

137:in 'size': No such file or directory - 01.mp3 (Errno::ENOENT)
from /Users/…/PrivatePodcast/generate_personal_podcast.rb:137:in 'block in <main>'
from /Users/…/PrivatePodcast/generate_personal_podcast.rb:72:in 'each'
from /Users/…/PrivatePodcast/generate_personal_podcast.rb:72:in '<main>'

But all the files are in the same directory. The podcast.rss is created in my user root directory, though. And it contains no files in the code, just the podcast properties.

Any hint for me? Thanks a lot in advance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.