umatic 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 78c8e30e9e7aae5beadf185bdd3e3b0078e716c4
4
+ data.tar.gz: 04cebcf00c8ca828d82957c7f71c6f45e05ab1b8
5
+ SHA512:
6
+ metadata.gz: 0ece0fdb1031dc4c3fd49a3142d88cc18aea272f3921f9ef8321c69498fa993f7b2836cf3c3f382117d8f84946196a346c49444a71aa394e0203073e139df803
7
+ data.tar.gz: 565e16410db4b54762082fac755e3e3998788dfd92b9e7b767cde64c4987ec1ca0438bc875eebc9141109525cf51b27f711b901425dc5d6b45dd6e9fbe2e303e
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013 Nazar Kanaev (nkanaev@live.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ # Umatic
2
+ **CLI tool for video downloading**
3
+
4
+ Supports following websites:
5
+
6
+ * Youtube
7
+ * Vimeo
@@ -0,0 +1,36 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ #############################################
5
+
6
+ desc "Running Tests"
7
+ task :test do
8
+ Rake::TestTask.new do |t|
9
+ t.libs.push "lib"
10
+ t.test_files = FileList['test/test_*.rb']
11
+ end
12
+ end
13
+
14
+ #############################################
15
+
16
+ spec = "umatic.gemspec"
17
+ gemspec = eval(File.read(spec))
18
+ gemfile = "#{gemspec.full_name}.gem"
19
+
20
+ desc "Building Gem"
21
+ task :build => gemfile
22
+
23
+ file gemfile => gemspec.files + [spec] do
24
+ `gem build #{spec}`
25
+ end
26
+
27
+ #############################################
28
+
29
+ desc "Installing Gem"
30
+ task :install => :build do
31
+ `gem install #{gemfile}`
32
+ end
33
+
34
+ #############################################
35
+
36
+ task :default => [:test, :build, :install]
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'umatic'
4
+
5
+ module Umatic
6
+ class Application
7
+ SCREEN_BOUNDS = 80
8
+
9
+ def initialize(argv)
10
+ @argv = argv
11
+ end
12
+
13
+ def select total
14
+ return 1 if total == 1
15
+ while 1
16
+ print "Enter your choice [0 to cancel]: "
17
+ input = $stdin.gets.chomp
18
+ num = input.to_i
19
+ next unless num.to_s == input
20
+ return num if (0..total).member? num
21
+ end
22
+ 0
23
+ end
24
+
25
+
26
+ def run
27
+ help if ARGV[0] == 'help'
28
+
29
+ @url = ARGV.last
30
+ fail "Please provide an URL to download from" unless @url
31
+
32
+ channel = Channel.open @url
33
+ fail "No matching channel found" unless channel
34
+
35
+ result = channel.process
36
+ fail "Unable to get videos" unless result
37
+
38
+ line '='
39
+ puts " Title: #{result[:title]}"
40
+ puts "Description: #{result[:description]}"
41
+ line '='
42
+
43
+ sources = result[:sources]
44
+ max_fmt = sources.map { |s| s.format.length }.max
45
+ max_inf = SCREEN_BOUNDS - 5 - max_fmt
46
+ sources.each_index do |i|
47
+ source = sources[i]
48
+ puts "#{(i+1).to_s.rjust(3)} #{source.info.ljust(max_inf)} #{source.format}"
49
+ line '-'
50
+ end
51
+
52
+ choice = select sources.count
53
+ abort if choice == 0
54
+ download(result, sources[choice-1])
55
+ end
56
+
57
+ def safe_file(filename)
58
+ filename.gsub(/(\\|\/|\'|\"|:|\(|\))/, "")
59
+ end
60
+
61
+ def download(result, source)
62
+ filename = Dir.pwd + '/' + safe_file(result[:title]) + '.' + source.format
63
+ url = source.url.dump
64
+
65
+ if command_exists? 'curl'
66
+ system "curl -o '#{filename}' #{url}"
67
+ elsif command_exists? 'wget'
68
+ system "wget -O '#{filename}' #{url}"
69
+ else
70
+ abort("No supported downloader found")
71
+ end
72
+ end
73
+
74
+ def command_exists?(cmd)
75
+ return true
76
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
77
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
78
+ exts.each { |ext|
79
+ exe = File.join(path, "#{cmd}#{ext}")
80
+ return true if File.executable? exe
81
+ }
82
+ end
83
+ return false
84
+ end
85
+
86
+ def line char
87
+ SCREEN_BOUNDS.times do
88
+ print char
89
+ end
90
+ puts
91
+ end
92
+
93
+ def fail reason
94
+ puts reason
95
+ puts "Type 'help' for some info"
96
+ exit
97
+ end
98
+
99
+ def help
100
+ app = File.basename __FILE__
101
+
102
+ puts app
103
+ puts "CLI tool for video downloading"
104
+ puts
105
+
106
+ puts "Usage:"
107
+ puts " #{app} url"
108
+ puts
109
+
110
+ puts "Supported Video Services:"
111
+ Umatic.supported_channels.each do |c|
112
+ puts " #{c.to_s}"
113
+ end
114
+ exit
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ Umatic::Application.new(ARGV).run
@@ -0,0 +1,41 @@
1
+ require 'open-uri'
2
+ require 'json'
3
+ require 'cgi'
4
+ require 'ostruct'
5
+ require File.expand_path('../../client.rb', __FILE__)
6
+
7
+ module Umatic
8
+ class Channel
9
+ def self.channels; @@channels; end
10
+
11
+ def self.inherited(subclass)
12
+ @@channels ||= []
13
+ @@channels << subclass
14
+ end
15
+
16
+ def self.open(url)
17
+ @@channels.each do |c|
18
+ return c.new(url) if c.supports? url
19
+ end
20
+ nil
21
+ end
22
+
23
+ def self.supports? url
24
+ false
25
+ end
26
+
27
+ def initialize(url)
28
+ @url = url
29
+ end
30
+
31
+ def parse(page)
32
+ raise "Parsing not implemented for #{self.class}"
33
+ end
34
+
35
+ def process
36
+ page = HTTPClient.instance.get @url
37
+ parse page
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,55 @@
1
+ $:.push File.expand_path('../', __FILE__)
2
+ require 'channel'
3
+
4
+ module Umatic
5
+ class Vimeo < Channel
6
+ def self.supports? url
7
+ url.match(/https?:\/\/vimeo.com\/[\d+]{6,10}/)
8
+ end
9
+
10
+ def parse page
11
+ page.delete!("\n")
12
+ re = / = {config:(.+),assets:/
13
+ json_match = page.match(re)
14
+ raise "Unable to retrieve video info" unless json_match
15
+ json = json_match[1]
16
+
17
+ config = JSON.parse(json)
18
+ title = config['video']['title']
19
+ description = ''
20
+ sources = []
21
+
22
+ sig = config['request']['signature']
23
+ timestamp = config['request']['timestamp']
24
+ video_id = config['video']['id']
25
+
26
+ files = config['video']['files']
27
+ files.each do |codec, qualities|
28
+ qualities.each do |quality|
29
+ s = OpenStruct.new
30
+ s.format = 'mp4'
31
+ s.url = correct_url "http://player.vimeo.com/play_redirect?"\
32
+ "clip_id=#{video_id}"\
33
+ "&sig=#{sig}"\
34
+ "&time=#{timestamp}"\
35
+ "&quality=#{quality}"\
36
+ "&codecs=#{codec.upcase}"\
37
+ "&type=moogaloop_local"\
38
+ "&embed_location=&seek=0"
39
+ s.info = "#{codec} - #{quality}"
40
+ sources << s
41
+ end
42
+ end
43
+
44
+ {
45
+ :title => title,
46
+ :description => description,
47
+ :sources => sources
48
+ }
49
+ end
50
+
51
+ def correct_url(url)
52
+ HTTPClient.instance.open(url)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,62 @@
1
+ $:.push File.expand_path('../', __FILE__)
2
+ require 'channel'
3
+
4
+ module Umatic
5
+ class Youtube < Channel
6
+ def self.supports? url
7
+ url.match(/http[s]?:\/\/www.youtube.com\/watch?.*v=.+/) != nil
8
+ end
9
+
10
+ QUALITY = {
11
+ # low quality
12
+ '5' => { :f => 'flv', :d => '240p' },
13
+ '6' => { :f => 'flv', :d => '270p'},
14
+ '13' => { :f => '3gp', :d => 'N/A' },
15
+ '17' => { :f => '3gp', :d => '144p' },
16
+ '18' => { :f => 'mp4', :d => '360p' },
17
+
18
+ # medium quality
19
+ '34' => { :f => 'flv', :d => '360p' },
20
+ '35' => { :f => 'flv', :d => '480p' },
21
+ '43' => { :f => 'webm', :d => '360p' },
22
+ '44' => { :f => 'webm', :d => '480p' },
23
+
24
+ # high quality
25
+ '22' => { :f => 'mp4', :d => '720p' },
26
+ '37' => { :f => 'mp4', :d => '1080p' },
27
+ '38' => { :f => 'mp4', :d => '3072p' },
28
+ '45' => { :f => 'webm', :d => '720p' },
29
+ '46' => { :f => 'webm', :d => '1080p' }
30
+ }
31
+
32
+ def parse page
33
+ json_match = page.match(/<script>.+ytplayer.config = (.+).+?<\/script>/)
34
+ raise "Unable to retrieve video info" unless json_match
35
+ info = JSON::parse json_match[1]
36
+
37
+ title = info['args']['title']
38
+ description = ''
39
+ sources = []
40
+
41
+ maps = info["args"]["url_encoded_fmt_stream_map"]
42
+ maps.split(',').each do |m|
43
+ params = CGI::parse(m)
44
+ params.each { |k, v| params[k] = v[0] }
45
+ itag = params['itag']
46
+ next unless QUALITY.keys.include?(itag)
47
+
48
+ s = OpenStruct.new
49
+ s.format = QUALITY[itag][:f]
50
+ s.info = QUALITY[itag][:d]
51
+ s.url = "#{params['url']}&signature=#{params['sig']}"
52
+ sources << s
53
+ end
54
+
55
+ {
56
+ :title => title,
57
+ :description => description,
58
+ :sources => sources
59
+ }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module Umatic
5
+ class HTTPClient
6
+
7
+ def self.instance
8
+ @@instance ||= new
9
+ end
10
+
11
+ def open(uri_str, limit = 2)
12
+ raise 'HTTP redirect too deep' if limit == 0
13
+
14
+ uri = URI.parse(uri_str)
15
+
16
+ http = Net::HTTP.new(uri.host, uri.port)
17
+ http.use_ssl = true if uri.scheme == "https"
18
+ response = http.head(uri.path + '?' + (uri.query ? uri.query : ''))
19
+
20
+ case response
21
+ when Net::HTTPSuccess then uri_str
22
+ when Net::HTTPRedirection then open(response['location'], limit - 1)
23
+ else
24
+ response.error!
25
+ end
26
+ end
27
+
28
+ def get(url)
29
+ uri = open(url)
30
+ Net::HTTP.get(URI.parse(uri))
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ class Proc
2
+ def callback(callable, *args)
3
+ self === Class.new do
4
+ method_name = callable.to_sym
5
+ define_method(method_name) { |&block| block.nil? ? true : block.call(*args) }
6
+ define_method("#{method_name}?") { true }
7
+ def method_missing(method_name, *args, &block) false; end
8
+ end.new
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ module Umatic
2
+ VERSION = '0.1'
3
+ end
4
+
5
+ $:.push File.expand_path('../', __FILE__)
6
+
7
+ require 'std/proc'
8
+ require 'channel/channel'
9
+ require 'channel/youtube'
10
+ require 'channel/vimeo'
11
+
12
+ module Umatic
13
+ def self.open url
14
+ Channel.open url
15
+ end
16
+
17
+ def self.supported_channels
18
+ Channel.channels.map { |c| c.to_s.gsub(/Umatic::/, "") }
19
+ end
20
+
21
+ end
@@ -0,0 +1,26 @@
1
+ require 'umatic'
2
+ require 'minitest/autorun'
3
+
4
+ describe Umatic::HTTPClient do
5
+
6
+ it "can find correct url with redirections" do
7
+ link = "http://google.com/"
8
+ page = Umatic::HTTPClient.instance.open(link)
9
+ page.wont_be_nil
10
+ page.must_be_instance_of String
11
+ page.must_equal "http://www.google.com/"
12
+ end
13
+
14
+ it "can fetch url content" do
15
+ content = Umatic::HTTPClient.instance.get("http://www.google.com/")
16
+ content.wont_be_nil
17
+ content.must_be_instance_of String
18
+ content.must_match(/I'm Feeling Lucky/)
19
+ end
20
+
21
+ it "can even fetch https url content" do
22
+ content = Umatic::HTTPClient.instance.get("https://www.google.com/")
23
+ content.wont_be_nil
24
+ end
25
+
26
+ end
Binary file
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'umatic'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "umatic"
7
+ s.authors = ["Nazar Kanaev"]
8
+ s.email = "nazar.kanaev@gmail.com"
9
+ s.homepage = "http://github.com/nkanaev/umatic"
10
+ s.version = Umatic::VERSION
11
+ s.platform = Gem::Platform::RUBY
12
+ s.summary = "umatic"
13
+ s.description = "CLI tool for video downloading"
14
+ s.license = "MIT"
15
+
16
+ s.add_dependency "curl"
17
+ s.add_development_dependency "minitest"
18
+
19
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
20
+ s.test_files = Dir["./test/*"]
21
+ s.executables = ["umatic"]
22
+ s.require_paths = ["lib"]
23
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: umatic
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Nazar Kanaev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curl
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: CLI tool for video downloading
42
+ email: nazar.kanaev@gmail.com
43
+ executables:
44
+ - umatic
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ./README.md
49
+ - ./LICENSE
50
+ - ./Gemfile
51
+ - ./umatic.gemspec
52
+ - ./umatic-0.1.gem
53
+ - ./lib/channel/youtube.rb
54
+ - ./lib/channel/vimeo.rb
55
+ - ./lib/channel/channel.rb
56
+ - ./lib/std/proc.rb
57
+ - ./lib/umatic.rb
58
+ - ./lib/client.rb
59
+ - ./Rakefile
60
+ - ./test/test_client.rb
61
+ - bin/umatic
62
+ homepage: http://github.com/nkanaev/umatic
63
+ licenses:
64
+ - MIT
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 2.0.3
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: umatic
86
+ test_files:
87
+ - ./test/test_client.rb