Skip to content

Instantly share code, notes, and snippets.

@venj
Created February 6, 2012 16:43
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save venj/1753206 to your computer and use it in GitHub Desktop.
Save venj/1753206 to your computer and use it in GitHub Desktop.
This is a script that helps you download any apple keynote presentation on http://www.apple.com/apple-events/. You can copy any of the keynote broadcast link, pick a resolution and feed to this script, this script will help you download the stream video.
#!/usr/bin/env ruby
#
# Name: Apple keynote stream video downloader
# Author: venj<i[AT]venj.me>
# Date: 2012-09-13
#
# Description:
# This script helps you download apple keynote stream video on
# http://www.apple.com/apple-events/. Video format is TS.
#
# This script uses many dirty code to make it possible to download
# all the videos that is currently available on that URL.
#
# This script tried its best to be future proof (and it proves!),
# but Apple changes the stream video file format frequently, so it
# may not be able to download future keynotes. But I will update
# this script if any change happens.
#
# How to use:
# ./down_video resolution broadcast_link
#
# Example:
# ./down_video 720p http://www.apple.com/apple-events/education-january-2012/
#
# Update Notes:
# 2012-09-13 Added support for download September 2012 apple event.
# Now show a nicer fragment count while downloading.
# 2012-03-08 Fixed a bug that will cause open-uri error under Ruby 1.8.7
# 2012-03-08 Update resolution fields, for 3 different size 720 sized video(no 1080).
# 2012-02-07 First script that can download all apple keynotes stream video finished.
#
require "open-uri"
resolution = ARGV[0]
weblink = ARGV[1]
size_arr = ["720p", "720", "720i", "540p", "540i", "360p", "360", "360i", "224p", "224i"]
size_hash = {"720p" => "6540", "720" => "4540", "720i" => "3340", "540p" => "2540", "540i" => "1840", "360p" => "1240", "360" => "0640", "360i" => "0440", "224p" => "0240", "224i" => "0150"}
if ["--help", "help", "-h"].include?(resolution) || ARGV.size < 2
puts "Usage: #{File.basename __FILE__} resolution broadcast_link"
puts "\nPossible resolution values: \n\t#{size_arr.join(', ')}"
exit 0
end
unless size_arr.include?(resolution)
puts "Usage: #{File.basename __FILE__} resolution broadcast_link"
puts "\nPossible resolution values: \n\t#{size_arr.join(', ')}"
exit 0
end
m3u_link = ""
puts "Parsing webpage..."
#puts open(weblink).read
#exit
begin
m3u_link = open(weblink).read.match(/http:\/\/.+?\.m3u8/)[0]
rescue NoMethodError => e
begin # Handles 1 possible javascript redirection.
redirect_link = open(weblink).read.match(/http:\/\/.+?\.html/)[0]
m3u_link = open(redirect_link).read.match(/http:\/\/.+?\.m3u8/)[0]
rescue NoMethodError => e
begin
urljs = open(weblink).read.match(/http:\/\/.+?\/url\.js/)[0] # For september-2012 event.
m3u_link = open(urljs).read.match(/http:\/\/.+?\.m3u8/)[0]
rescue Exception => e
puts "No video found."
exit 1
end
end
rescue Exception => e
puts "Unknown error."
exit 1
end
playlist_links = []
puts "Parsing outter m3u8 file..."
m3u = open(m3u_link).each_line do |line|
playlist_links << line if line =~ /^[^#]/ && line.strip != ""
end
available_resolutions = []
playlist_links.each do |l|
rate = ""
segments_count = l.split("/").size
case segments_count # A dirty way to identify different m3u8 link structure
when 6 # Modern format with full link
rate = File.basename(File.dirname(l))
when 5 # Previous format with full link
rate = l.split("/").last.split(".").first
if !size_hash.values.include?(rate)
rate = File.basename(l).split("_").first
end
when 1 # Previous format with only file name.
rate = l.split(/(\.|_)/).first
else
puts "Unknown outter m3u8 format. \n\nPlease file a bug with failed link."
exit 1
end
available_resolutions << (size_hash.invert)[rate]
end
unless available_resolutions.include?(resolution)
puts "Resolution #{resolution} is not available."
# Ignore the audio track in one particular case: apple-events/september-2010.
puts "Available resolutions: #{available_resolutions.compact.sort.join(', ')}"
exit
end
puts "Parsing inner m3u8 file..."
link_index = available_resolutions.index(resolution)
outfilename = "video_#{resolution}.ts"
File.unlink outfilename if File.exists? outfilename
video_url = playlist_links[link_index]
unless video_url =~ /^http/ # Handles inner m3u8 with only filename listed in outter m3u8
video_url = "#{File.dirname(m3u_link)}/#{video_url}"
end
outfile = open outfilename, 'w+'
ts_fragments_list = []
open(video_url).each_line do |line|
next if line =~ /^#/
ts_fragments_list << line
end
counter = 1;fragments_count = ts_fragments_list.size
ts_fragments_list.each do |line|
ts_fragment = ""
if line.include?("http://")
ts_fragment = line
else
ts_fragment = "#{File.dirname(video_url)}/#{line.strip}"
end
print "Download fragment (#{counter}/#{fragments_count})..."
infile = open ts_fragment
outfile.write infile.read
infile.close
counter += 1
puts "Done"
end
outfile.close
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment