#! /usr/bin/env ruby require 'rubygems' require 'fileutils' require 'mechanize' require 'logger' require 'ostruct' require 'open-uri' require 'optparse' class MuxRip LOG = Logger.new STDOUT LOG.level = Logger::INFO class << self def log_level(lvl) LOG.level = lvl end # Download the HTML files for a set of Muxtapes def grab(names) new_mixtapes = {} agent = WWW::Mechanize.new names.each do |name| link = "http://" + name + ".muxtape.com" file_name = Tempfile.new(name).path new_mixtapes[name] = file_name LOG.info "Downloading #{name}..." agent.get(link).save_as(file_name) end new_mixtapes end # Scrape the Muxtape pages for track names and URLs def analyze(mixtapes) mixtape_songs = {} mixtapes.each do |name,file_name| LOG.info("Analyzing #{name}...") page = Hpricot(open(file_name)) # Find script that passes "Kettle" the necessary Amazon keys # and scrape the song id/key pairs. These aren't necessarily # in playlist order. code_map = {} (page/"script").each do |script| src = script.inner_text if src =~ /new\s+Kettle\(\[([^\]]+)\],\[([^\]]+)\]/ ids, codes = [$1, $2].map {|a| a.gsub("'",'').split(",") } ids.zip(codes).each do |ic| code_map["#{ic[0]}"] = ic[1] end end end # Now walk the playlist, scrape the names, and build the URLs songs = [] (page/"li.song").each do |li| id = li.get_attribute('id').sub(/^song/,'') code = code_map[id] song = (li/'div.name').inner_text.strip LOG.debug("#{id}, #{code}, '#{song}'") songs << OpenStruct.new(:name => "#{song}", :url => "http://muxtape.s3.amazonaws.com/songs/#{id}?#{code}") end mixtape_songs[name] = songs end mixtape_songs end # Download Muxtape MP3s def download(mixtapes) mixtape_songs = {} mixtapes.each do |mixtape, songs| LOG.info("Downloading songs for #{mixtape}...") FileUtils.mkdir_p("#{mixtape}") dl_songs = [] songs.each do |song| num_tries = 3 begin song_file = "#{mixtape}/#{song.name}.mp3" LOG.info("Downloading #{song.name}...") LOG.debug("Retrieving #{song.url}") open(song.url) do |f| open(song_file,"wb") {|mp3| mp3.write f.read } end dl_songs << song rescue num_tries -= 1 LOG.info("Error downloading: #{num_tries} left") next if num_tries == 0 retry end end mixtape_songs[mixtape] = dl_songs end mixtape_songs end # Dump a Muxtape to M3U format def m3u(mixtapes) mixtapes.each do |mixtape,songs| m3u_file = "#{mixtape}/#{mixtape}.m3u" LOG.info("Creating playlist #{m3u_file}...") output = File.open(m3u_file,"w+") output.write("#EXTM3U\n") songs.each do |song| output.write("#EXTINF:-1,#{song.name}\n#{song.name}.mp3\n") end output.close end end # Import a Muxtape into iTunes def itunes(mixtapes) LOG.info("Launching iTunes...") i_tunes = app('iTunes') mixtapes.each do |mixtape,songs| next if i_tunes.playlists[its.name.eq(mixtape)].exists #skip if exists LOG.info("Creating playlist #{mixtape}...") pl = i_tunes.make(:new => :user_playlist, :with_properties => {:name => mixtape}) LOG.info("Adding files...") songs.each do |song| i_tunes.add(MacTypes::FileURL.path(File.expand_path(File.dirname(__FILE__) + "/#{mixtape}/#{song.name}.mp3")), :to => pl) end end end end end options = { :m3u => true, :itunes => false } name = File.basename($0, '.*') parser = OptionParser.new("Usage: #{$0} [options] MUXTAPE [MUXTAPE ...]") parser.on('-h', '--help', 'display usage information') do puts parser exit end parser.on('-q', '--quiet', 'suppress normal output') do MuxRip.log_level( Logger::WARN ) end parser.on('--nom3u', 'do not create an M3U playlist') do options[:m3u] = false end parser.on('--itunes', 'import Muxtape to iTunes') do options[:itunes] = true end # Unmatched command-line arguments should be Muxtape names mixtapes = parser.parse($*) results = MuxRip.download( MuxRip.analyze( MuxRip.grab(mixtapes) ) ) if options[:m3u] MuxRip.m3u( results ) end if options[:itunes] require "appscript" include Appscript MuxRip.itunes( results ) end