#!/opt/local/bin/ruby # $Id$ # $URL$ # # Copyright (c) 2006 Ward Wouts # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # ######################################## # the configuration bit @podcasts = { # the podcast name "distortedview" => { # where's the rss file "rss" => 'http://www.distortedview.com/show/index.xml', # where should enclosures be saved "savedir" => '/Users/ward/Private/mp3/[books]/Distorted View', # rename magic, this is used on the file name with sub! "rename" => [ /_(\d\d\d\d)(\d\d)/, '\2\1' ], # directory to put symlinks to the new files "linkdir" => '/Users/ward/ipod_sync/[books]/Distorted View', # option to write an m3u file after fetching the mp3s "m3u" => '/Users/ward/dv.m3u', }, # "tagesschau" => { # "rss" => 'http://www.tagesschau.de/export/podcast', # "savedir" => '/Users/ward/ipod_sync/[books]/Tagesschau', # # delete files that aren't in the rss anylonger # "delete" => true, # }, # "pennradio" => { # "rss" => 'http://penn.freefm.com/pages/podcast/431.rss', # # %%DATE%% is a magic word here, gets replaced by the pubdate in # # YYYYMMDD format. Won't work if there is no pubDate in the rss feed # "rename" => [ /^/, '%%DATE%%-' ], # "savedir" => '/Users/ward/Private/mp3/[books]/PennRadio', # "linkdir" => '/Users/ward/ipod_sync/[books]/PennRadio', # }, "bsdtalk" => { "rss" => 'http://feeds.feedburner.com/Bsdtalk', "savedir" => '/Users/ward/Private/mp3/[books]/BSDTalk', "linkdir" => '/Users/ward/ipod_sync/[books]/BSDTalk', }, } ######################################## # code from here on require 'net/http' require 'uri' require 'rexml/document' require 'date' def fetch(uri_str, limit = 10) # You should choose better exception. raise ArgumentError, 'HTTP redirect too deep' if limit == 0 response = Net::HTTP.get_response(URI.parse(uri_str)) case response when Net::HTTPSuccess then response when Net::HTTPRedirection then fetch(response['location'], limit - 1) else response.error! end end def deleteold(podcast) Dir.open(@podcasts[podcast]["savedir"]).each {|entry| if File.file?("#{@podcasts[podcast]["savedir"]}/#{entry}") && ! @filelist[entry] puts " deleting #{entry}" File.unlink("#{@podcasts[podcast]["savedir"]}/#{entry}") end } end def getenclosure(podcast, item) enclosure = nil item.each_element{|x| if x.name == "enclosure" enclosure = x end } pubdate = item.get_elements("pubDate")[0] date = nil if ! pubdate.nil? date = Date.parse(pubdate.text).strftime("%Y%m%d") end if ! enclosure.nil? && ! enclosure.attribute("url").nil? cast = enclosure.attribute("url").to_s.dup filename = cast.dup filename.sub!(/^.*\//, "") if @podcasts[podcast]["rename"] replacement = @podcasts[podcast]["rename"][1].dup if replacement.match(/%%DATE%%/) replacement.sub!(/%%DATE%%/, date) end filename.sub!(@podcasts[podcast]["rename"][0], replacement) end @filelist[filename] = true if ! File.exists?("#{@podcasts[podcast]["savedir"]}/#{filename}") puts " getting #{@podcasts[podcast]["savedir"]}/#{filename}" begin response = fetch(cast) File.open("#{@podcasts[podcast]["savedir"]}/#{filename}", "w"){|f| f.print(response.body) } if @podcasts[podcast]["linkdir"] File.symlink("#{@podcasts[podcast]["savedir"]}/#{filename}", "#{@podcasts[podcast]["linkdir"]}/#{filename}") end return "#{@podcasts[podcast]["savedir"]}/#{filename}" rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Net::HTTPFatalError puts " error #{$!} fetching, skipping" end else return "#{@podcasts[podcast]["savedir"]}/#{filename}" end end end for podcast in @podcasts.keys.sort files = Array.new puts podcast begin res = fetch(@podcasts[podcast]["rss"]) @filelist = {} # body.gsub!(/#{13.chr}#{10.chr}/, "#{10.chr}") xmldoc = REXML::Document.new(res.body) xmldoc.elements.each("rss/channel") {|item| item.each_element{|x| if x.name == "item" files.push getenclosure(podcast, x) end } } if @podcasts[podcast]["delete"] deleteold(podcast) end if @podcasts[podcast]["m3u"] File.open(@podcasts[podcast]["m3u"], "w"){|f| files.each{|file| f.puts file } } end rescue rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Net::HTTPFatalError puts " error #{$!} fetching, skipping" end end