viddl-rb 0.8 → 0.61
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +14 -0
- data/Gemfile.lock +7 -29
- data/README.md +34 -122
- data/Rakefile +8 -22
- data/bin/viddl-rb +117 -45
- data/helper/download-helper.rb +67 -94
- data/helper/plugin-helper.rb +10 -60
- data/plugins/blip.rb +2 -3
- data/plugins/metacafe.rb +54 -53
- data/plugins/soundcloud.rb +15 -19
- data/plugins/veoh.rb +41 -40
- data/plugins/vimeo.rb +23 -30
- data/plugins/youtube.rb +138 -266
- metadata +29 -50
- data/TODO.txt +0 -3
- data/bin/helper/downloader.rb +0 -20
- data/bin/helper/driver.rb +0 -47
- data/bin/helper/parameter-parser.rb +0 -109
- data/helper/audio-helper.rb +0 -48
- data/helper/utility-helper.rb +0 -45
- data/lib/viddl-rb.rb +0 -103
- data/plugins/dailymotion.rb +0 -44
data/CHANGELOG.txt
ADDED
data/Gemfile.lock
CHANGED
@@ -1,43 +1,21 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
viddl-rb (0.
|
5
|
-
|
6
|
-
nokogiri (~> 1.5.0)
|
7
|
-
progressbar
|
4
|
+
viddl-rb (0.6)
|
5
|
+
nokogiri
|
8
6
|
|
9
7
|
GEM
|
10
8
|
remote: http://rubygems.org/
|
11
9
|
specs:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
mechanize (2.7.1)
|
17
|
-
domain_name (~> 0.5, >= 0.5.1)
|
18
|
-
http-cookie (~> 1.0.0)
|
19
|
-
mime-types (~> 1.17, >= 1.17.2)
|
20
|
-
net-http-digest_auth (~> 1.1, >= 1.1.1)
|
21
|
-
net-http-persistent (~> 2.5, >= 2.5.2)
|
22
|
-
nokogiri (~> 1.4)
|
23
|
-
ntlm-http (~> 0.1, >= 0.1.1)
|
24
|
-
webrobots (>= 0.0.9, < 0.2)
|
25
|
-
mime-types (1.23)
|
26
|
-
minitest (5.0.4)
|
27
|
-
net-http-digest_auth (1.3)
|
28
|
-
net-http-persistent (2.8)
|
29
|
-
nokogiri (1.5.10)
|
30
|
-
ntlm-http (0.1.1)
|
31
|
-
progressbar (0.20.0)
|
32
|
-
rake (10.0.4)
|
10
|
+
mime-types (1.17.2)
|
11
|
+
minitest (2.11.3)
|
12
|
+
nokogiri (1.5.0)
|
13
|
+
rake (0.9.2.2)
|
33
14
|
rest-client (1.6.7)
|
34
15
|
mime-types (>= 1.16)
|
35
|
-
unf (0.1.1)
|
36
|
-
unf_ext
|
37
|
-
unf_ext (0.0.6)
|
38
|
-
webrobots (0.1.1)
|
39
16
|
|
40
17
|
PLATFORMS
|
18
|
+
java
|
41
19
|
ruby
|
42
20
|
|
43
21
|
DEPENDENCIES
|
data/README.md
CHANGED
@@ -1,122 +1,34 @@
|
|
1
|
-
__viddl-rb:__
|
2
|
-
|
3
|
-
Repo: http://github.com/rb2k/viddl-rb
|
4
|
-
[![
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
The --save-dir option works with both absolute and relative paths (relative based on the directory viddl-rb is run from).
|
36
|
-
If you want to save to a folder with spaces in it, you have to quote the path like this: --save-dir "C:/my videos"
|
37
|
-
|
38
|
-
__Youtube plugin specifics:__
|
39
|
-
|
40
|
-
Download all videos on a playlist:
|
41
|
-
```viddl-rb http://www.youtube.com/playlist?list=PL7E8DA0A515924126```
|
42
|
-
|
43
|
-
Download all videos from a user:
|
44
|
-
```viddl-rb http://www.youtube.com/user/tedtalksdirector```
|
45
|
-
|
46
|
-
Filter videos to download from a user/playlist:
|
47
|
-
```viddl-rb http://www.youtube.com/user/tedtalksdirector --filter /internet/i```
|
48
|
-
|
49
|
-
The --filter argument accepts a regular expression and will only download videos where the title matches the regex.
|
50
|
-
It uses the same syntax as Ruby regular expression literals do.
|
51
|
-
|
52
|
-
__Library Usage:__
|
53
|
-
|
54
|
-
```ruby
|
55
|
-
require 'viddl-rb'
|
56
|
-
|
57
|
-
download_urls = ViddlRb.get_urls("http://www.youtube.com/watch?v=QH2-TGUlwu4")
|
58
|
-
download_urls.first # => "http://o-o.preferred.arn06s04.v3.lscac ..."
|
59
|
-
```
|
60
|
-
|
61
|
-
The ViddlRb module has the following module public methods:
|
62
|
-
|
63
|
-
* __get_urls_names(url)__
|
64
|
-
-- Returns an array of one or more hashes that has the keys :url which
|
65
|
-
points to the download url and :name which points to the name
|
66
|
-
(which is a filename safe version of the video title with a file extension).
|
67
|
-
Returns nil if the url is not recognized by any plugins.
|
68
|
-
|
69
|
-
* __get_urls_exts(url)__
|
70
|
-
-- Same as get_urls_names but with just the file extension (for example ".mp4")
|
71
|
-
instead of the full filename, and the :name key is replaced with :ext.
|
72
|
-
Returns nil if the url is not recognized by any plugins.
|
73
|
-
|
74
|
-
* __get_urls(url)__
|
75
|
-
-- Returns an array of download urls for the specified video url.
|
76
|
-
Returns nil if the url is not recognized by any plugins.
|
77
|
-
|
78
|
-
* __get_names(url)__
|
79
|
-
-- Returns an array of filenames for the specified video url.
|
80
|
-
Returns nil if the url is not recognized by any plugins.
|
81
|
-
|
82
|
-
* __io=(io_object)__
|
83
|
-
-- By default all plugin output to stdout will be suppressed when the library is used.
|
84
|
-
If you are interested in the output of a plugin, you can set an IO object that
|
85
|
-
will receive all plugin output using this method. For example:
|
86
|
-
|
87
|
-
```ruby
|
88
|
-
require 'viddl-rb'
|
89
|
-
|
90
|
-
ViddlRb.io = $stdout # plugins will now write their output to $stdout
|
91
|
-
```
|
92
|
-
|
93
|
-
All the __get__ methods in the ViddlRb module will raise either a ViddlRb::PluginError or a ViddlRb::DownloadError if the plugin fails.
|
94
|
-
A ViddlRb::PluginError is raised if the plugin fails in an unexpected way, and a ViddlRb::DownloadError is raised if the video could not be downloaded for some reason.
|
95
|
-
An example of that is if a Youtube video is not embeddable - then it can't be downloaded.
|
96
|
-
|
97
|
-
```ruby
|
98
|
-
begin
|
99
|
-
ViddlRb.get_urls(video_url)
|
100
|
-
rescue ViddlRb::DownloadError => e
|
101
|
-
puts "Could not get download url: #{e.message}"
|
102
|
-
rescue ViddlRb::PluginError => e
|
103
|
-
puts "Plugin blew up! #{e.message}\n" +
|
104
|
-
"Backtrace:\n#{e.backtrace.join("\n")}"
|
105
|
-
end
|
106
|
-
```
|
107
|
-
|
108
|
-
__Requirements:__
|
109
|
-
|
110
|
-
* curl/wget or the [progress bar](http://github.com/nex3/ruby-progressbar/) gem
|
111
|
-
* [Nokogiri](http://nokogiri.org/)
|
112
|
-
* [Mechanize](http://mechanize.rubyforge.org/)
|
113
|
-
* ffmpeg if you want to extract audio tracks from the videos
|
114
|
-
|
115
|
-
__Co Maintainer:__
|
116
|
-
* [kl](https://github.com/kl): Windows support (who knew!), bug fixes, veoh plugin, metacafe plugin, refactoring it into a library, ...
|
117
|
-
|
118
|
-
__Contributors:__
|
119
|
-
* [divout](https://github.com/divout) aka Ivan K: blip.tv plugin, bugfixes
|
120
|
-
* Sniper: bugfixes
|
121
|
-
* [Serabe](https://github.com/Serabe) aka Sergio Arbeo: packaging viddl as a binary
|
122
|
-
* [laserlemon](https://github.com/laserlemon): Adding gemnasium images to readme
|
1
|
+
__viddl-rb:__
|
2
|
+
Created by Marc Seeger (@rb2k)
|
3
|
+
Repo: http://github.com/rb2k/viddl-rb
|
4
|
+
[![Build Status](https://secure.travis-ci.org/rb2k/viddl-rb.png)](http://travis-ci.org/rb2k/viddl-rb)
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
__Installation:__
|
9
|
+
gem install viddl-rb
|
10
|
+
|
11
|
+
__Usage:__
|
12
|
+
|
13
|
+
Download a video:
|
14
|
+
viddl-rb http://www.youtube.com/watch?v=QH2-TGUlwu4
|
15
|
+
|
16
|
+
Download a video and extract the audio:
|
17
|
+
viddl-rb http://www.youtube.com/watch?v=QH2-TGUlwu4 --extract-audio
|
18
|
+
|
19
|
+
In both cases we'll name the output file according to the video title.
|
20
|
+
|
21
|
+
|
22
|
+
__Requirements:__
|
23
|
+
|
24
|
+
* curl/wget or the [progress bar](http://github.com/nex3/ruby-progressbar/) gem
|
25
|
+
* [Nokogiri](http://nokogiri.org/)
|
26
|
+
* ffmpeg if you want to extract audio tracks from the videos
|
27
|
+
|
28
|
+
|
29
|
+
__Contributors:__
|
30
|
+
|
31
|
+
* [kl](https://github.com/kl): Windows support (who knew!), bug fixes, veoh plugin, metacafe plugin
|
32
|
+
* [divout](https://github.com/divout) aka Ivan K: blip.tv plugin, bugfixes
|
33
|
+
* Sniper: bugfixes
|
34
|
+
* [Serabe](https://github.com/Serabe) aka Sergio Arbeo: packaging viddl as a binary
|
data/Rakefile
CHANGED
@@ -1,22 +1,8 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require '
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
t.test_files = ["spec/lib_spec.rb", "spec/url_extraction_spec.rb", "spec/integration_spec.rb"]
|
10
|
-
end
|
11
|
-
|
12
|
-
Rake::TestTask.new(:test_lib) do |t|
|
13
|
-
t.test_files = FileList["spec/lib_spec.rb"]
|
14
|
-
end
|
15
|
-
|
16
|
-
Rake::TestTask.new(:test_extract) do |t|
|
17
|
-
t.test_files = FileList["spec/url_extraction_spec.rb"]
|
18
|
-
end
|
19
|
-
|
20
|
-
Rake::TestTask.new(:test_integration) do |t|
|
21
|
-
t.test_files = FileList["spec/integration_spec.rb"]
|
22
|
-
end
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :default => [:test]
|
5
|
+
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.pattern = "spec/*_spec.rb"
|
8
|
+
end
|
data/bin/viddl-rb
CHANGED
@@ -1,45 +1,117 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'helper')
|
3
|
-
|
4
|
-
|
5
|
-
require "
|
6
|
-
require "
|
7
|
-
require "
|
8
|
-
require "
|
9
|
-
require "
|
10
|
-
require "
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'helper')
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require "nokogiri"
|
6
|
+
require "cgi"
|
7
|
+
require "open-uri"
|
8
|
+
require "open3"
|
9
|
+
require "download-helper.rb"
|
10
|
+
require "plugin-helper.rb"
|
11
|
+
|
12
|
+
if ARGV[0].nil?
|
13
|
+
puts "Usage: viddl-rb URL [--extract-audio]"
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
|
17
|
+
puts "Loading Plugins"
|
18
|
+
Dir[File.join(File.dirname(__FILE__),"../plugins/*.rb")].each do |plugin|
|
19
|
+
load plugin
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "Plugins loaded: #{PluginBase.registered_plugins.inspect}"
|
23
|
+
|
24
|
+
url = ARGV[0]
|
25
|
+
extract_audio = ARGV.include?('--extract-audio')
|
26
|
+
url_only = ARGV.include?('--url-only')
|
27
|
+
title_only = ARGV.include?('--title-only')
|
28
|
+
|
29
|
+
puts "Will try to extract audio: #{extract_audio}."
|
30
|
+
|
31
|
+
unless url.match(/^http/)
|
32
|
+
puts "Please include 'http' with your URL e.g. http://www.youtube.com/watch?v=QH2-TGUlwu4"
|
33
|
+
exit(1)
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "Analyzing URL: #{url}"
|
37
|
+
#Check all plugins for a match
|
38
|
+
PluginBase.registered_plugins.each do |plugin|
|
39
|
+
if plugin.matches_provider?(url)
|
40
|
+
puts "#{plugin}: true"
|
41
|
+
begin
|
42
|
+
#we'll end up with an array of hashes with they keys :url and :name
|
43
|
+
download_queue = plugin.get_urls_and_filenames(url)
|
44
|
+
rescue StandardError => e
|
45
|
+
puts "Error while running the #{plugin.name.inspect} plugin. Maybe it has to be updated? Error: #{e.message}."
|
46
|
+
exit(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
if url_only
|
50
|
+
download_queue.each{|url_name| puts url_name[:url]}
|
51
|
+
exit
|
52
|
+
elsif title_only
|
53
|
+
download_queue.each{|url_name| puts url_name[:name]}
|
54
|
+
exit
|
55
|
+
end
|
56
|
+
|
57
|
+
download_queue.each do |url_name|
|
58
|
+
result = DownloadHelper.save_file(url_name[:url], url_name[:name])
|
59
|
+
if result
|
60
|
+
puts "Download for #{url_name[:name]} successful."
|
61
|
+
if extract_audio
|
62
|
+
puts "Extracting audio for #{url_name[:name]}"
|
63
|
+
if DownloadHelper.os_has?('ffmpeg')
|
64
|
+
no_ext_filename = url_name[:name].split('.')[0..-1][0]
|
65
|
+
#capture stderr because ffmpeg expects an output param and will error out
|
66
|
+
puts "Gathering information about the downloaded file."
|
67
|
+
file_info = Open3.popen3("ffmpeg -i #{url_name[:name]}") {|stdin, stdout, stderr, wait_thr| stderr.read }
|
68
|
+
puts "Done gathering information about the downloaded file."
|
69
|
+
if !file_info.to_s.empty?
|
70
|
+
audio_format_matches = file_info.match(/Audio: (\w*)/)
|
71
|
+
if audio_format_matches
|
72
|
+
audio_format = audio_format_matches[1]
|
73
|
+
puts "detected audio format: #{audio_format}"
|
74
|
+
else
|
75
|
+
puts "Couldn't find any audio:\n#{file_info.inspect}"
|
76
|
+
next
|
77
|
+
end
|
78
|
+
|
79
|
+
extension_mapper = {
|
80
|
+
'aac' => 'm4a',
|
81
|
+
'mp3' => 'mp3',
|
82
|
+
'vorbis' => 'ogg'
|
83
|
+
}
|
84
|
+
|
85
|
+
if extension_mapper.key?(audio_format)
|
86
|
+
output_extension = extension_mapper[audio_format]
|
87
|
+
else
|
88
|
+
#lame fallback
|
89
|
+
puts "Unknown audio format: #{audio_format}, using name as extension: '.#{audio_format}'."
|
90
|
+
output_extension = audio_format
|
91
|
+
end
|
92
|
+
output_filename = "#{no_ext_filename}.#{output_extension}"
|
93
|
+
if File.exist?(output_filename)
|
94
|
+
puts "Audio file seems to exist already, removing it before extraction."
|
95
|
+
File.delete(output_filename)
|
96
|
+
end
|
97
|
+
Open3.popen3("ffmpeg -i #{url_name[:name]} -vn -acodec copy #{output_filename}") {|stdin, stdout, stderr, wait_thr| stdout.read }
|
98
|
+
puts "Done extracting audio to #{output_filename}"
|
99
|
+
else
|
100
|
+
puts "Error while checking audio track of #{url_name[:name]}"
|
101
|
+
end
|
102
|
+
else
|
103
|
+
puts "Didn't detect ffmpeg on your system, can't extract audio."
|
104
|
+
end
|
105
|
+
end
|
106
|
+
else
|
107
|
+
puts "Download for #{url_name[:name]} failed."
|
108
|
+
end
|
109
|
+
end
|
110
|
+
#plugin matched and downloaded, we're done
|
111
|
+
exit
|
112
|
+
else
|
113
|
+
puts "#{plugin}: false"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
puts "No plugin seems to feel responsible for this URL."
|
data/helper/download-helper.rb
CHANGED
@@ -1,94 +1,67 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
def self.download_and_save_file(download_url, full_path)
|
69
|
-
final_url = UtilityHelper.get_final_location(download_url) # follow all redirects
|
70
|
-
uri = URI(final_url)
|
71
|
-
file = File.new(full_path, "wb")
|
72
|
-
file_size = 0
|
73
|
-
|
74
|
-
Net::HTTP.start(uri.host, uri.port) do |http|
|
75
|
-
http.request_get(uri.request_uri) do |res|
|
76
|
-
file_size = res.read_header["content-length"].to_i
|
77
|
-
bar = ProgressBar.new(File.basename(full_path), file_size)
|
78
|
-
bar.file_transfer_mode
|
79
|
-
res.read_body do |segment|
|
80
|
-
bar.inc(segment.size)
|
81
|
-
file.write(segment)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
file.close
|
86
|
-
print "\n"
|
87
|
-
download_successful?(full_path, file_size) #because Net::HTTP.start does not throw Net exceptions
|
88
|
-
end
|
89
|
-
|
90
|
-
def self.download_successful?(full_file_path, file_size)
|
91
|
-
File.exist?(full_file_path) && File.size(full_file_path) == file_size
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
1
|
+
class DownloadHelper
|
2
|
+
#usually not called directly
|
3
|
+
def self.fetch_file(uri)
|
4
|
+
|
5
|
+
begin
|
6
|
+
require "progressbar" #http://github.com/nex3/ruby-progressbar
|
7
|
+
rescue LoadError
|
8
|
+
puts "ERROR: You don't seem to have curl or wget on your system. In this case you'll need to install the 'progressbar' gem."
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
progress_bar = nil
|
12
|
+
open(uri, :proxy => nil,
|
13
|
+
:content_length_proc => lambda { |length|
|
14
|
+
if length && 0 < length
|
15
|
+
progress_bar = ProgressBar.new(uri.to_s, length)
|
16
|
+
end
|
17
|
+
},
|
18
|
+
:progress_proc => lambda { |progress|
|
19
|
+
progress_bar.set(progress) if progress_bar
|
20
|
+
}) {|file| return file.read}
|
21
|
+
end
|
22
|
+
|
23
|
+
#simple helper that will save a file from the web and save it with a progress bar
|
24
|
+
def self.save_file(file_uri, file_name)
|
25
|
+
unescaped_uri = CGI::unescape(file_uri)
|
26
|
+
result = false
|
27
|
+
if os_has?("wget")
|
28
|
+
puts "using wget"
|
29
|
+
result = system("wget \"#{unescaped_uri}\" -O #{file_name}")
|
30
|
+
elsif os_has?("curl")
|
31
|
+
puts "using curl"
|
32
|
+
#-L means: follow redirects, We set an agent because Vimeo seems to want one
|
33
|
+
result = system("curl -A 'Wget/1.8.1' -L \"#{unescaped_uri}\" -o #{file_name}")
|
34
|
+
else
|
35
|
+
puts "using net/http"
|
36
|
+
open(file_name, 'wb') { |file|
|
37
|
+
file.write(fetch_file(unescaped_uri)); puts
|
38
|
+
}
|
39
|
+
result = true
|
40
|
+
end
|
41
|
+
result
|
42
|
+
end
|
43
|
+
|
44
|
+
#checks to see whether the os has a certain utility like wget or curl
|
45
|
+
def self.os_has?(utility)
|
46
|
+
windows = ENV['OS'] =~ /windows/i
|
47
|
+
return `which #{utility}`.include?(utility) unless windows # if not Windows
|
48
|
+
|
49
|
+
#use where (simliar to which) if present to reduce console clutter
|
50
|
+
begin
|
51
|
+
has_where? ? `where #{utility}` : `#{utility}`
|
52
|
+
return true
|
53
|
+
rescue Errno::ENOENT
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
#checks if Windows has the where utility (Server 2003 and later)
|
59
|
+
def self.has_where?
|
60
|
+
begin
|
61
|
+
`where`
|
62
|
+
true
|
63
|
+
rescue Errno::ENOENT
|
64
|
+
false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|