coderrr-video-accel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,10 @@
1
+ Video what?
2
+ ====
3
+
4
+ video-accel is an accelerator for streaming videos on the web. It acts very similar to a download accelerator.
5
+
6
+ A download accelerator will split a file up into a small (~5) number of even parts. It will then download each part simultaneously. This gets past the per-connection bandwidth cap and allows you to max out your total bandwidth cap. The problem with this is that it doesn't help you watch videos while they're downloading since the first 1/5 of the file will still be downloading at normal speed, and that's the part you want to start watching now.
7
+
8
+ So instead of splitting the file up into a small number of even parts, video-accel splits the file up into a large number of even parts and then downloads a small number at a time, but it downloads them *in order*. The fact that it downloads them in order is what allows you to still stream the video while it is being downloaded at a faster rate.
9
+
10
+ Currently video-accel only works with Linux.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ load 'video-accel.gemspec'
data/bin/vdl ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ load File.dirname(__FILE__) + '/../lib/vdl.rb'
data/bin/vplayer ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.dirname(__FILE__) + "/lib"
4
+
5
+ require 'video_player'
6
+
7
+ VideoPlayer.new(ARGV[0]).play
@@ -0,0 +1,21 @@
1
+ require 'uri'
2
+
3
+ class ClipNabber
4
+ URI = ::URI.parse("http://clipnabber.com/gethint.php?mode=1")
5
+
6
+ def initialize(url)
7
+ @url = url
8
+ end
9
+
10
+ def download_link
11
+ body = Net::HTTP.start(URI.host, URI.port) do |h|
12
+ h.request_get(URI.request_uri + "&url=#{@url}&sid=#{rand}", 'Referer' => 'http://clipnabber.com').body
13
+ end
14
+
15
+ if body =~ %r{<a href='([^']+).*?><strong>FLV download link}im
16
+ $1
17
+ else
18
+ nil
19
+ end
20
+ end
21
+ end
data/lib/core_ext.rb ADDED
@@ -0,0 +1,17 @@
1
+ class IO
2
+ def while_reading(data = nil, &b)
3
+ while buf = readpartial_rescued(16384)
4
+ data << buf if data
5
+ yield buf if block_given?
6
+ end
7
+ data
8
+ end
9
+
10
+ private
11
+
12
+ def readpartial_rescued(size)
13
+ readpartial(size)
14
+ rescue EOFError
15
+ nil
16
+ end
17
+ end
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'net/http'
4
+ require 'cgi'
5
+ require 'threadpool'
6
+ require 'core_ext'
7
+ Thread.abort_on_exception = true
8
+
9
+ class SpeedStream
10
+ USER_AGENT = %{Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3}
11
+ BUF_SIZE = 1024
12
+
13
+ attr_reader :uri, :bytes_per_conn, :output_file, :file_handle, :concurrent_connections, :cookies
14
+ attr_accessor :on_progress
15
+
16
+ def initialize(uri, output_file)
17
+ @uri = URI.parse(uri)
18
+ @output_file = output_file
19
+ @write_mutex = Mutex.new
20
+ @finished = []
21
+ @bytes_per_conn = 30_000
22
+ @concurrent_connections = 10
23
+ @cookies = {}
24
+ @extra_file_length_headers = {'Range' => 'bytes=1-'}
25
+ end
26
+
27
+ def download!
28
+ @start_time = Time.now
29
+ @bytes_written = 0
30
+ pool = ThreadPool.new(concurrent_connections, 999999)
31
+
32
+ File.open(output_file, "wb") do |f|
33
+ @file_handle = f
34
+
35
+ begin
36
+ ranges.each do |range|
37
+ pool.add_work(range) do |range|
38
+ download_range range
39
+ end
40
+ end
41
+ ensure
42
+ pool.shutdown
43
+ end
44
+ end
45
+ end
46
+
47
+
48
+ private
49
+
50
+ def download_range(range)
51
+ file_offset = range.begin
52
+
53
+ data = get_with_redirects(range)
54
+ write_file(file_offset, data)
55
+
56
+ process_progress(range)
57
+ end
58
+
59
+ def process_progress(range)
60
+ pos = range.begin/bytes_per_conn
61
+ @finished[pos] = 1
62
+ total_pos = file_length/bytes_per_conn
63
+ time_passed = Time.now - @start_time
64
+
65
+ completed_count = (@finished.index(nil)||@finished.size)
66
+ completed_bytes = completed_count * bytes_per_conn
67
+ bps = 1.0 * @bytes_written/time_passed
68
+ percent = 100.0 * completed_count / total_pos
69
+
70
+ on_progress.call(completed_bytes, bps, percent) if on_progress
71
+ end
72
+
73
+ def get_with_redirects(range)
74
+ loop do
75
+ Net::HTTP.start(uri.host, uri.port) do |h|
76
+ req = h.request_get(uri.request_uri, 'Range' => "bytes=#{range.begin}-#{range.end}", 'Cookie' => cookie_string)
77
+ if loc = req['location']
78
+ @uri = URI.parse(loc)
79
+ next
80
+ end
81
+
82
+ return req.body
83
+ end
84
+ end
85
+ end
86
+
87
+ def write_file(pos, buf)
88
+ @bytes_written += buf.size
89
+ @write_mutex.synchronize do
90
+ file_handle.seek(pos, IO::SEEK_SET)
91
+ file_handle.write buf
92
+ file_handle.flush
93
+ end
94
+ end
95
+
96
+ def ranges
97
+ @ranges ||= begin
98
+ rs = []
99
+ (file_length/bytes_per_conn).times do |n|
100
+ rs << (n*bytes_per_conn..(n+1)*bytes_per_conn)
101
+ end
102
+ rs[-1] = (rs[-1].begin..file_length)
103
+ rs
104
+ end
105
+ end
106
+
107
+ def file_length
108
+ @file_length ||= begin
109
+ current_uri = uri
110
+ loop do
111
+ Net::HTTP.start(current_uri.host, current_uri.port) do |h|
112
+ headers = {'Host' => current_uri.host, 'User-Agent' => USER_AGENT, 'Cookie' => cookie_string}
113
+ headers.merge! @extra_file_length_headers
114
+ resp = h.request_head(current_uri.request_uri, headers)
115
+
116
+ cookies.merge! parse_cookies(resp['set-cookie']) if resp['set-cookie']
117
+
118
+ if loc = resp['location']
119
+ current_uri = URI.parse(loc)
120
+ next
121
+ end
122
+
123
+ @uri = current_uri
124
+ return resp['content-length'].to_i
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def parse_cookies(header)
131
+ Hash[*header.scan(/(?:^|, )(\w+)=([^;]+)/).flatten]
132
+ end
133
+
134
+ def cookie_string
135
+ cookies.map {|k,v| "#{k}=#{v}"}.join '; '
136
+ end
137
+ end
data/lib/threadpool.rb ADDED
@@ -0,0 +1,115 @@
1
+ require 'thread'
2
+ require 'monitor'
3
+
4
+ class ThreadPool
5
+
6
+ class PoolStopped < Exception; end
7
+
8
+ def initialize(thread_size=10, queue_size=100)
9
+ @mutex = Monitor.new
10
+ @cv = @mutex.new_cond
11
+ @queue = []
12
+ @max_queue_size = queue_size
13
+ @threads = []
14
+ @stopped = false
15
+ thread_size.times { @threads << Thread.new { start_worker } }
16
+ end
17
+
18
+ def add_work(*args, &callback)
19
+ push_task(Task.new(*args, &callback))
20
+ end
21
+
22
+ def push_task(task)
23
+ @mutex.synchronize do
24
+ raise PoolStopped.new if @stopped
25
+ @cv.wait_while { @max_queue_size > 0 && @queue.size >= @max_queue_size }
26
+ @queue.push(task)
27
+ @cv.broadcast
28
+ end
29
+ task
30
+ end
31
+
32
+ def pop_task
33
+ task = nil
34
+ @mutex.synchronize do
35
+ @cv.wait_while { @queue.size == 0 }
36
+ task = @queue.shift
37
+ @cv.broadcast
38
+ end
39
+ task
40
+ end
41
+
42
+ def shutdown
43
+ @mutex.synchronize do
44
+ @stopped = true
45
+ @threads.each { @queue.push(:stop) }
46
+ @cv.broadcast
47
+ end
48
+ @threads.each { |thread| thread.join }
49
+ end
50
+
51
+ def start_worker
52
+ while true
53
+ task = pop_task
54
+ return if task == :stop
55
+ task.execute
56
+ end
57
+ end
58
+
59
+ # wait for current work to complete
60
+ def sync
61
+ tasks = @mutex.synchronize { @queue.dup }
62
+ tasks.each { |task| task.join }
63
+ end
64
+
65
+ class Task
66
+
67
+ attr_reader :result, :exception
68
+
69
+ def initialize(*args, &callback)
70
+ @args = args
71
+ @callback = callback
72
+ @done = false
73
+ @result = nil
74
+ @exception = nil
75
+ @mutex = Monitor.new
76
+ @cv = @mutex.new_cond
77
+ end
78
+
79
+ def execute
80
+ begin
81
+ @result = @callback.call(*@args)
82
+ rescue Exception => e
83
+ @exception = e
84
+ STDERR.puts "Error in thread #{Thread.current} - #{e}"
85
+ e.backtrace.each { |element| STDERR.puts(element) }
86
+ end
87
+ @mutex.synchronize do
88
+ @done = true
89
+ @cv.broadcast
90
+ end
91
+ end
92
+
93
+ def join
94
+ @mutex.synchronize { @cv.wait_until { @done } }
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ #
101
+ #tasks = []
102
+ #tp = ThreadPool.new(10, 1000)
103
+ #sleep(1)
104
+ #100.times do |id|
105
+ # STDERR.puts "adding work"
106
+ # tasks << tp.add_work do
107
+ # puts "Running #{id} #{Thread.current}"
108
+ # sleep 5
109
+ # puts "Ending #{id} #{Thread.current}"
110
+ # end
111
+ #end
112
+ #
113
+ #puts "Waiting for shutdown"
114
+ #tp.shutdown
115
+ #puts "done"
data/lib/vdl.rb ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.dirname(__FILE__)
4
+ require 'optparse'
5
+ require 'fileutils'
6
+ require 'clip_nabber'
7
+ require 'youtube_speed_stream'
8
+
9
+ skip_resolve = nil
10
+ player = "vplayer"
11
+ terminal = "gnome-terminal -e"
12
+
13
+ (opts = OptionParser.new do |o|
14
+ o.on("-s", "--skip-resolve") { skip_resolve = true }
15
+ o.on("-p", "--player CMD") { |a| player = a }
16
+ o.on("-t", "--terminal CMD") { |a| terminal = a }
17
+ end).parse! rescue (puts opts; exit)
18
+ url = ARGV.shift
19
+ url = "http://#{url}" if url !~ /https?:\/\//i
20
+ puts "URL: #{url}"
21
+
22
+ # random name
23
+ ltrs=('a'..'z').to_a
24
+ name = ([nil]*3).map{ltrs[rand(ltrs.size)]}*''
25
+ dir = File.expand_path('~/.movies')
26
+ FileUtils.mkdir_p dir
27
+ file = File.join dir, name
28
+
29
+ unless skip_resolve
30
+ puts "resolving url"
31
+ dl_url = ClipNabber.new(url).download_link
32
+ if ! (URI.parse(dl_url) rescue false)
33
+ puts "problem resolving #{url}"
34
+ exit
35
+ end
36
+ puts "resolved to #{dl_url}"
37
+ url = dl_url
38
+ end
39
+
40
+ klass = (url =~ /youtube\.com\//) ? YoutubeSpeedStream : SpeedStream
41
+ stream = klass.new url, file
42
+
43
+ started = false
44
+ stream.on_progress = lambda do |bytes, bps, percent|
45
+ print "\ec"
46
+ puts "downloading #{url} to #{file}..."
47
+ puts "#{'%.2f'%bps} b/s #{'%.2f%'%percent}"
48
+
49
+ if !started and bytes > 1_000_000
50
+ started = true
51
+ system(%{#{terminal} "#{player} #{file}" &})
52
+ end
53
+ end
54
+
55
+ stream.download!
@@ -0,0 +1,54 @@
1
+ class VideoPlayer
2
+ MP_ARGS = [
3
+ "",
4
+ "-autosync 30 -mc 2.0",
5
+ "-autosync 0 -mc 0.0"
6
+ ]
7
+
8
+ def initialize(file)
9
+ @file = file
10
+ @mplayer_arg = 0
11
+ end
12
+
13
+ def wait_for_cmd
14
+ rdy = IO.select([STDIN])
15
+ if rdy.first.include? STDIN
16
+ process_cmd STDIN.getc.chr
17
+ end
18
+ end
19
+
20
+ def process_cmd(cmd)
21
+ puts "process #{cmd}"
22
+ case cmd
23
+ when 'q' then exit!
24
+ when 'm'
25
+ @pid.kill! if @pid
26
+ @pid = spawn("mplayer -msglevel all=0 #{MP_ARGS[@mplayer_arg]} #{@file}")
27
+ @mplayer_arg = (@mplayer_arg + 1)%MP_ARGS.size
28
+ when 'v'
29
+ @pid.kill! if @pid
30
+ @pid = spawn("vlc #{@file}")
31
+ end
32
+ end
33
+
34
+ def spawn(cmd)
35
+ pid = fork do
36
+ exec(cmd)
37
+ end
38
+
39
+ o = Object.new
40
+ (class <<o;self;end).send :define_method, :kill! do
41
+ puts "killing #{pid}"
42
+ ret = Process.kill 9, pid
43
+ end
44
+
45
+ o
46
+ end
47
+
48
+ def play
49
+ process_cmd('m')
50
+ loop do
51
+ wait_for_cmd
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ require 'speed_stream'
2
+
3
+ class YoutubeSpeedStream < SpeedStream
4
+ def initialize(*a)
5
+ super
6
+ @concurrent_connections = 20
7
+ @extra_file_length_headers = {}
8
+ end
9
+
10
+ private
11
+
12
+ def get_with_redirects(range)
13
+ current_uri = uri
14
+
15
+ loop do
16
+ TCPSocket.open(current_uri.host, current_uri.port) do |s|
17
+ uri_str = current_uri.request_uri
18
+ if uri_str.include? "&start="
19
+ uri_str.gsub(/&start=\d+/, "&start=#{range.begin}")
20
+ else
21
+ uri_str << "&start=#{range.begin}"
22
+ end
23
+ s.write("GET #{uri_str} HTTP/1.1\r\nHost: #{current_uri.host}\r\nCookie: #{cookie_string}\r\n\r\n")
24
+
25
+ s.while_reading(data = "") { break if data =~ /\r?\n\r?\n/ }
26
+
27
+ if data =~ /^Location: (.+?)\r?$/
28
+ current_uri = URI.parse($1)
29
+ next
30
+ end
31
+
32
+ # strip off header and keep reading
33
+ data.gsub!(/\A.+?\r?\n\r?\n/m,'')
34
+
35
+ while buf = (s.readpartial(16384) rescue nil)
36
+ data << buf
37
+ break if range.begin + data.size > range.end
38
+ end
39
+
40
+ # strip FLV headers on all parts except first
41
+ start = range.begin == 0 ? 0 : 13
42
+ return data[start, range.end - range.begin]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,14 @@
1
+ spec = Gem::Specification.new do |s|
2
+ s.name = "video-accel"
3
+ s.version = "0.0.1"
4
+ s.author = "coderrr"
5
+ s.email = "coderrr.contact@gmail.com"
6
+ # s.homepage = "http://blogs.cocoondev.org/crafterm/"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = "accelerate video streams"
9
+ s.files = ["lib/vdl.rb", "lib/speed_stream.rb", "lib/youtube_speed_stream.rb", "lib/threadpool.rb", "lib/core_ext.rb", "lib/clip_nabber.rb", "lib/video_player.rb", "bin/vdl", "bin/vplayer"] +
10
+ %w(Rakefile README.markdown video-accel.gemspec)
11
+ s.require_path = "lib"
12
+ s.bindir = "bin"
13
+ s.executables = ["vdl", "vplayer"]
14
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: coderrr-video-accel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - coderrr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: coderrr.contact@gmail.com
18
+ executables:
19
+ - vdl
20
+ - vplayer
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - lib/vdl.rb
27
+ - lib/speed_stream.rb
28
+ - lib/youtube_speed_stream.rb
29
+ - lib/threadpool.rb
30
+ - lib/core_ext.rb
31
+ - lib/clip_nabber.rb
32
+ - lib/video_player.rb
33
+ - bin/vdl
34
+ - bin/vplayer
35
+ - Rakefile
36
+ - README.markdown
37
+ - video-accel.gemspec
38
+ has_rdoc: false
39
+ homepage:
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.2.0
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: accelerate video streams
64
+ test_files: []
65
+