ez_video 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +41 -0
- data/LICENSE.txt +20 -0
- data/README.md +132 -0
- data/ez_video.gemspec +28 -0
- data/lib/ez_video.rb +15 -0
- data/lib/rvideo/errors.rb +27 -0
- data/lib/rvideo/float.rb +7 -0
- data/lib/rvideo/inspector.rb +573 -0
- data/lib/rvideo/tools/abstract_tool.rb +339 -0
- data/lib/rvideo/tools/ffmpeg.rb +208 -0
- data/lib/rvideo/tools/ffmpeg2theora.rb +42 -0
- data/lib/rvideo/tools/flvtool2.rb +50 -0
- data/lib/rvideo/tools/mencoder.rb +106 -0
- data/lib/rvideo/tools/mp4box.rb +21 -0
- data/lib/rvideo/tools/mp4creator.rb +35 -0
- data/lib/rvideo/tools/mplayer.rb +31 -0
- data/lib/rvideo/tools/yamdi.rb +44 -0
- data/lib/rvideo/transcoder.rb +138 -0
- data/lib/rvideo/version.rb +9 -0
- metadata +65 -0
data/.gitignore
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Compiled source #
|
2
|
+
###################
|
3
|
+
*.com
|
4
|
+
*.class
|
5
|
+
*.dll
|
6
|
+
*.exe
|
7
|
+
*.o
|
8
|
+
*.so
|
9
|
+
*.gem
|
10
|
+
.yardoc
|
11
|
+
|
12
|
+
# Packages #
|
13
|
+
############
|
14
|
+
# it's better to unpack these files and commit the raw source
|
15
|
+
# git has its own built in compression methods
|
16
|
+
*.7z
|
17
|
+
*.dmg
|
18
|
+
*.gz
|
19
|
+
*.iso
|
20
|
+
*.jar
|
21
|
+
*.rar
|
22
|
+
*.tar
|
23
|
+
*.zip
|
24
|
+
|
25
|
+
# Logs and databases #
|
26
|
+
######################
|
27
|
+
*.log
|
28
|
+
*.sql
|
29
|
+
*.sqlite
|
30
|
+
|
31
|
+
# OS generated files #
|
32
|
+
######################
|
33
|
+
.DS_Store*
|
34
|
+
ehthumbs.db
|
35
|
+
Icon?
|
36
|
+
Thumbs.db
|
37
|
+
|
38
|
+
# IDE generated files #
|
39
|
+
#######################
|
40
|
+
.idea
|
41
|
+
*~
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Jonathan Dahl and Slantwise Design
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# EZHttp
|
2
|
+
|
3
|
+
A wrapper for ruby net/http, supports http/https, RESTful methods, headers, certificate and file uploads.
|
4
|
+
It supports both command line and ruby code.
|
5
|
+
|
6
|
+
## How to use it
|
7
|
+
|
8
|
+
### Command Line
|
9
|
+
|
10
|
+
Send with query string
|
11
|
+
|
12
|
+
ezhttp \
|
13
|
+
--url "https://api.twitter.com/1/followers/ids.json" \
|
14
|
+
--method "get" \
|
15
|
+
--data "cursor=-1&screen_name=twitterapi"
|
16
|
+
|
17
|
+
Send with query string
|
18
|
+
|
19
|
+
ezhttp \
|
20
|
+
--url "https://api.twitter.com/1/followers/ids.json&cursor=-1&screen_name=twitterapi" \
|
21
|
+
--method "get"
|
22
|
+
|
23
|
+
Send with json
|
24
|
+
|
25
|
+
ezhttp \
|
26
|
+
--url 'http://127.0.0.1:3000/file/upload_file' \
|
27
|
+
--data '{"name":{"fn":"xxx","ln":"xxx"}}' \
|
28
|
+
--method 'post' \
|
29
|
+
--type 'application/json'
|
30
|
+
|
31
|
+
Send with header
|
32
|
+
|
33
|
+
ezhttp \
|
34
|
+
--url 'https://api.twitter.com/oauth/request_token' \
|
35
|
+
--method 'post' \
|
36
|
+
--header 'Authorization: OAuth oauth_nonce="K7ny27JTpKVsTgdyLdDfmQQWVLERj2zAK5BslRsqyw", oauth_callback="http%3A%2F%2Fmyapp.com%3A3005%2Ftwitter%2Fprocess_callback", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1300228849", oauth_consumer_key="OqEqJeafRSF11jBMStrZz", oauth_signature="Pc%2BMLdv028fxCErFyi8KXFM%2BddU%3D", oauth_version="1.0"'
|
37
|
+
|
38
|
+
|
39
|
+
Upload file
|
40
|
+
|
41
|
+
ezhttp \
|
42
|
+
--url 'http://127.0.0.1:3000/file/upload_file' \
|
43
|
+
--files 'path_to_file.png'
|
44
|
+
|
45
|
+
Upload multiple files with header
|
46
|
+
|
47
|
+
ezhttp \
|
48
|
+
--url 'http://127.0.0.1:3000/file/upload_file' \
|
49
|
+
--files 'path_to_file1.zip','path_to_file2.jpg' \
|
50
|
+
--header 'authorization:Basic Zvsdwegbdgegsdv0xvsd='
|
51
|
+
|
52
|
+
### Ruby
|
53
|
+
|
54
|
+
Send with encoded query string as data
|
55
|
+
|
56
|
+
# Post request
|
57
|
+
response = EZHttp.Post("https://www.example.com:83/api",
|
58
|
+
"user_id=12345&token=sdfwD12g%7Ecc")
|
59
|
+
|
60
|
+
# Get request
|
61
|
+
response = EZHttp.Get("http://www.example.com/api",
|
62
|
+
"user_id=12345&token=sdfwD12g%7Ecc")
|
63
|
+
|
64
|
+
# OR
|
65
|
+
response = EZHttp.Get("http://www.example.com/api?user_id=12345&token=sdfwD12g%7Ecc")
|
66
|
+
|
67
|
+
Send with hash as data
|
68
|
+
|
69
|
+
# Post request
|
70
|
+
response = EZHttp.Post("https://www.example.com:83/api",
|
71
|
+
{"name1"=>"value", "name2" => "value2"})
|
72
|
+
|
73
|
+
# Put request
|
74
|
+
response = EZHttp.Put("https://www.example.com:83/api",
|
75
|
+
{"name1"=>"value", "name2" => "value2"})
|
76
|
+
|
77
|
+
Send with extra headers
|
78
|
+
|
79
|
+
response = EZHttp.Post("https://www.example.com:83/api",
|
80
|
+
"user_id=12345&token=sdfwD12g%7Ecc",
|
81
|
+
nil,
|
82
|
+
[
|
83
|
+
"authentication:oAuth username=xxx&password=xxx",
|
84
|
+
"other_header:other_values"
|
85
|
+
])
|
86
|
+
|
87
|
+
Send with pem certificate
|
88
|
+
|
89
|
+
response = EZHttp.Delete("https://www.example.com:83/api",
|
90
|
+
{"user_id"=>"12345"},
|
91
|
+
"application/json",
|
92
|
+
nil,
|
93
|
+
"/path_to_cert.pem")
|
94
|
+
|
95
|
+
Upload file(s)
|
96
|
+
|
97
|
+
#
|
98
|
+
files = []
|
99
|
+
file = File.open("path_to_file.extension", "rb")
|
100
|
+
files.push({"name" => File.basename(file), "content" => file.read})
|
101
|
+
file.close
|
102
|
+
|
103
|
+
# simply upload file
|
104
|
+
response = EZHttp.Upload("https://www.example.com:83/api",
|
105
|
+
files)
|
106
|
+
|
107
|
+
# upload file with headers
|
108
|
+
response = EZHttp.Upload("https://www.example.com:83/api",
|
109
|
+
files,
|
110
|
+
["authorization:Basic Zvsdwegbdgegsdv0xvsd="])
|
111
|
+
|
112
|
+
Display response
|
113
|
+
|
114
|
+
puts response.body
|
115
|
+
|
116
|
+
## Installation
|
117
|
+
|
118
|
+
Add the following line to rails "Gemfile"
|
119
|
+
|
120
|
+
gem "ez_http"
|
121
|
+
|
122
|
+
then execute
|
123
|
+
|
124
|
+
$ bundle install
|
125
|
+
|
126
|
+
|
127
|
+
See [http://rubygems.org/gems/ez_http](http://rubygems.org/gems/ez_http "EZHttp RubyGem Page") for more details
|
128
|
+
|
129
|
+
## Authors
|
130
|
+
|
131
|
+
Tianyu Huang
|
132
|
+
|
data/ez_video.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
lib = File.expand_path("../lib/", __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
require "rvideo/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
|
8
|
+
# Basic info
|
9
|
+
s.name = "ez_video"
|
10
|
+
s.version = RVideo::VERSION::STRING
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.date = "2012-06-22"
|
13
|
+
s.summary = "A fork of Rvideo, have some bugs fixed."
|
14
|
+
s.description = "A video reader/converter, ruby wrapper for ffmpeg."
|
15
|
+
s.authors = ["Tianyu Huang"]
|
16
|
+
s.email = ["tianhsky@yahoo.com"]
|
17
|
+
s.homepage = "http://rubygems.org/gems/ez_video"
|
18
|
+
|
19
|
+
# Dependencies
|
20
|
+
#s.required_rubygems_version = ">= 1.8.22"
|
21
|
+
|
22
|
+
# Files
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
26
|
+
s.extra_rdoc_files = ["README.md"]
|
27
|
+
|
28
|
+
end
|
data/lib/ez_video.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rvideo/inspector'
|
2
|
+
require 'rvideo/float'
|
3
|
+
require 'rvideo/tools/abstract_tool'
|
4
|
+
require 'rvideo/tools/ffmpeg'
|
5
|
+
require 'rvideo/tools/mencoder'
|
6
|
+
require 'rvideo/tools/flvtool2'
|
7
|
+
require 'rvideo/tools/mp4box'
|
8
|
+
require 'rvideo/tools/mplayer'
|
9
|
+
require 'rvideo/tools/mp4creator'
|
10
|
+
require 'rvideo/tools/ffmpeg2theora'
|
11
|
+
require 'rvideo/tools/yamdi'
|
12
|
+
require 'rvideo/errors'
|
13
|
+
require 'rvideo/transcoder'
|
14
|
+
require 'active_support'
|
15
|
+
require 'shellwords'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RVideo
|
2
|
+
class TranscoderError < RuntimeError
|
3
|
+
class InvalidCommand < TranscoderError
|
4
|
+
end
|
5
|
+
|
6
|
+
class InvalidFile < TranscoderError
|
7
|
+
end
|
8
|
+
|
9
|
+
class InputFileNotFound < TranscoderError
|
10
|
+
end
|
11
|
+
|
12
|
+
class OutputFileNotFound < TranscoderError
|
13
|
+
end
|
14
|
+
|
15
|
+
class UnexpectedResult < TranscoderError
|
16
|
+
end
|
17
|
+
|
18
|
+
class ParameterError < TranscoderError
|
19
|
+
end
|
20
|
+
|
21
|
+
class UnknownError < TranscoderError
|
22
|
+
end
|
23
|
+
|
24
|
+
class UnknownTool < TranscoderError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/rvideo/float.rb
ADDED
@@ -0,0 +1,573 @@
|
|
1
|
+
module RVideo # :nodoc:
|
2
|
+
class Inspector
|
3
|
+
|
4
|
+
attr_reader :filename, :path, :full_filename, :raw_response, :raw_metadata
|
5
|
+
|
6
|
+
attr_accessor :ffmpeg_binary
|
7
|
+
|
8
|
+
#
|
9
|
+
# To inspect a video or audio file, initialize an Inspector object.
|
10
|
+
#
|
11
|
+
# file = RVideo::Inspector.new(options_hash)
|
12
|
+
#
|
13
|
+
# Inspector accepts three options: file, raw_response, and ffmpeg_binary.
|
14
|
+
# Either raw_response or file is required; ffmpeg binary is optional.
|
15
|
+
#
|
16
|
+
# :file is a path to a file to be inspected.
|
17
|
+
#
|
18
|
+
# :raw_response is the full output of "ffmpeg -i [file]". If the
|
19
|
+
# :raw_response option is used, RVideo will not actually inspect a file;
|
20
|
+
# it will simply parse the provided response. This is useful if your
|
21
|
+
# application has already collected the ffmpeg -i response, and you don't
|
22
|
+
# want to call it again.
|
23
|
+
#
|
24
|
+
# :ffmpeg_binary is an optional argument that specifies the path to the
|
25
|
+
# ffmpeg binary to be used. If a path is not explicitly declared, RVideo
|
26
|
+
# will assume that ffmpeg exists in the Unix path. Type "which ffmpeg" to
|
27
|
+
# check if ffmpeg is installed and exists in your operating system's path.
|
28
|
+
#
|
29
|
+
|
30
|
+
def initialize(options = {})
|
31
|
+
if options[:raw_response]
|
32
|
+
@raw_response = options[:raw_response]
|
33
|
+
elsif options[:file]
|
34
|
+
if options[:ffmpeg_binary]
|
35
|
+
@ffmpeg_binary = options[:ffmpeg_binary]
|
36
|
+
raise RuntimeError, "ffmpeg could not be found (trying #{@ffmpeg_binary})" unless FileTest.exist?(@ffmpeg_binary)
|
37
|
+
else
|
38
|
+
# assume it is in the unix path
|
39
|
+
raise RuntimeError, 'ffmpeg could not be found (expected ffmpeg to be found in the Unix path)' unless FileTest.exist?(`which ffmpeg`.chomp)
|
40
|
+
@ffmpeg_binary = "ffmpeg"
|
41
|
+
end
|
42
|
+
|
43
|
+
file = options[:file]
|
44
|
+
@filename = File.basename(file)
|
45
|
+
@path = File.dirname(file)
|
46
|
+
@full_filename = file
|
47
|
+
raise TranscoderError::InputFileNotFound, "File not found (#{@full_filename})" unless FileTest.exist?(@full_filename)
|
48
|
+
@raw_response = `#{@ffmpeg_binary} -i #{Shellwords.shellescape @full_filename} 2>&1`
|
49
|
+
else
|
50
|
+
raise ArgumentError, "Must supply either an input file or a pregenerated response" if options[:raw_response].nil? and file.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
metadata = metadata_match
|
54
|
+
|
55
|
+
if /Unknown format/i.match(@raw_response) || metadata.nil?
|
56
|
+
@unknown_format = true
|
57
|
+
elsif /Duration: N\/A/im.match(@raw_response)
|
58
|
+
# elsif /Duration: N\/A|bitrate: N\/A/im.match(@raw_response)
|
59
|
+
@unreadable_file = true
|
60
|
+
@raw_metadata = metadata[1] # in this case, we can at least still get the container type
|
61
|
+
else
|
62
|
+
@raw_metadata = metadata[1]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Returns true if the file can be read successfully. Returns false otherwise.
|
68
|
+
#
|
69
|
+
|
70
|
+
def valid?
|
71
|
+
if @unknown_format or @unreadable_file
|
72
|
+
false
|
73
|
+
else
|
74
|
+
true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Returns false if the file can be read successfully. Returns false otherwise.
|
80
|
+
#
|
81
|
+
|
82
|
+
def invalid?
|
83
|
+
!valid?
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# True if the format is not understood ("Unknown Format")
|
88
|
+
#
|
89
|
+
|
90
|
+
def unknown_format?
|
91
|
+
if @unknown_format
|
92
|
+
true
|
93
|
+
else
|
94
|
+
false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# True if the file is not readable ("Duration: N/A, bitrate: N/A")
|
100
|
+
#
|
101
|
+
|
102
|
+
def unreadable_file?
|
103
|
+
if @unreadable_file
|
104
|
+
true
|
105
|
+
else
|
106
|
+
false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Does the file have an audio stream?
|
112
|
+
#
|
113
|
+
|
114
|
+
def audio?
|
115
|
+
if audio_match.nil?
|
116
|
+
false
|
117
|
+
else
|
118
|
+
true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Does the file have a video stream?
|
124
|
+
#
|
125
|
+
|
126
|
+
def video?
|
127
|
+
if video_match.nil?
|
128
|
+
false
|
129
|
+
else
|
130
|
+
true
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Take a screengrab of a movie. Requires an input file and a time parameter, and optionally takes an output filename. If no output filename is specfied, constructs one.
|
136
|
+
#
|
137
|
+
# Three types of time parameters are accepted - percentage (e.g. 3%), time in seconds (e.g. 60 seconds), and raw frame (e.g. 37). Will raise an exception if the time in seconds or the frame are out of the bounds of the input file.
|
138
|
+
#
|
139
|
+
# Types:
|
140
|
+
# 37s (37 seconds)
|
141
|
+
# 37f (frame 37)
|
142
|
+
# 37% (37 percent)
|
143
|
+
# 37 (default to seconds)
|
144
|
+
#
|
145
|
+
# If a time is outside of the duration of the file, it will choose a frame at the 99% mark.
|
146
|
+
#
|
147
|
+
# Example:
|
148
|
+
#
|
149
|
+
# t = RVideo::Transcoder.new('path/to/input_file.mp4')
|
150
|
+
# t.capture_frame('10%') # => '/path/to/screenshot/input-10p.jpg'
|
151
|
+
#
|
152
|
+
|
153
|
+
def capture_frame(timecode, output_file = nil)
|
154
|
+
t = calculate_time(timecode)
|
155
|
+
unless output_file
|
156
|
+
output_file = "#{TEMP_PATH}/#{File.basename(@full_filename, ".*")}-#{timecode.gsub("%","p")}.jpg"
|
157
|
+
end
|
158
|
+
# do the work
|
159
|
+
# mplayer $input_file$ -ss $start_time$ -frames 1 -vo jpeg -o $output_file$
|
160
|
+
# ffmpeg -i $input_file$ -v nopb -ss $start_time$ -b $bitrate$ -an -vframes 1 -y $output_file$
|
161
|
+
command = "ffmpeg -i #{@full_filename} -ss #{t} -t 00:00:01 -r 1 -vframes 1 -f image2 #{output_file}"
|
162
|
+
Transcoder.logger.info("\nCreating Screenshot: #{command}\n")
|
163
|
+
frame_result = `#{command} 2>&1`
|
164
|
+
|
165
|
+
# Different versions of ffmpeg report errors differently when a screenshot cannot be extracted from
|
166
|
+
# a video given its set of options. Some versions return a non-zero exit code and report errors while
|
167
|
+
# others simply
|
168
|
+
unless File.exists?(output_file)
|
169
|
+
msg = <<-EOT.gsub(/(^\s+|\n)/, '')
|
170
|
+
This means that ffmpeg could not extract a screenshot from the video. It may indicate that
|
171
|
+
the video file was corrupt or that the requested frame to be captured was outside the length
|
172
|
+
of the video. Full command: #{command}
|
173
|
+
EOT
|
174
|
+
Transcoder.logger.error msg
|
175
|
+
raise TranscoderError::OutputFileNotFound, msg
|
176
|
+
end
|
177
|
+
|
178
|
+
Transcoder.logger.info("\nScreenshot results: #{frame_result}")
|
179
|
+
output_file
|
180
|
+
end
|
181
|
+
|
182
|
+
def calculate_time(timecode)
|
183
|
+
m = /\A([0-9\.\,]*)(s|f|%)?\Z/.match(timecode)
|
184
|
+
if m.nil? or m[1].nil? or m[1].empty?
|
185
|
+
raise TranscoderError::ParameterError, "Invalid timecode for frame capture: #{timecode}. Must be a number, optionally followed by s, f, or %."
|
186
|
+
end
|
187
|
+
|
188
|
+
case m[2]
|
189
|
+
when "s", nil
|
190
|
+
t = m[1].to_f
|
191
|
+
when "f"
|
192
|
+
t = m[1].to_f / fps.to_f
|
193
|
+
when "%"
|
194
|
+
# milliseconds / 1000 * percent / 100
|
195
|
+
t = (duration.to_i / 1000.0) * (m[1].to_f / 100.0)
|
196
|
+
else
|
197
|
+
raise TranscoderError::ParameterError, "Invalid timecode for frame capture: #{timecode}. Must be a number, optionally followed by s, f, or p."
|
198
|
+
end
|
199
|
+
|
200
|
+
if (t * 1000) > duration
|
201
|
+
calculate_time("99%")
|
202
|
+
else
|
203
|
+
t
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
#
|
208
|
+
# Returns the version of ffmpeg used, In practice, this may or may not be
|
209
|
+
# useful.
|
210
|
+
#
|
211
|
+
# Examples:
|
212
|
+
#
|
213
|
+
# SVN-r6399
|
214
|
+
# CVS
|
215
|
+
#
|
216
|
+
|
217
|
+
def ffmpeg_version
|
218
|
+
@ffmpeg_version = @raw_response.split("\n").first.split("version").last.split(",").first.strip
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# Returns the configuration options used to build ffmpeg.
|
223
|
+
#
|
224
|
+
# Example:
|
225
|
+
#
|
226
|
+
# --enable-mp3lame --enable-gpl --disable-ffplay --disable-ffserver
|
227
|
+
# --enable-a52 --enable-xvid
|
228
|
+
#
|
229
|
+
|
230
|
+
def ffmpeg_configuration
|
231
|
+
/(\s*configuration:)(.*)\n/.match(@raw_response)[2].strip
|
232
|
+
end
|
233
|
+
|
234
|
+
#
|
235
|
+
# Returns the versions of libavutil, libavcodec, and libavformat used by
|
236
|
+
# ffmpeg.
|
237
|
+
#
|
238
|
+
# Example:
|
239
|
+
#
|
240
|
+
# libavutil version: 49.0.0
|
241
|
+
# libavcodec version: 51.9.0
|
242
|
+
# libavformat version: 50.4.0
|
243
|
+
#
|
244
|
+
|
245
|
+
def ffmpeg_libav
|
246
|
+
/^(\s*lib.*\n)+/.match(@raw_response)[0].split("\n").each {|l| l.strip! }
|
247
|
+
end
|
248
|
+
|
249
|
+
#
|
250
|
+
# Returns the build description for ffmpeg.
|
251
|
+
#
|
252
|
+
# Example:
|
253
|
+
#
|
254
|
+
# built on Apr 15 2006 04:58:19, gcc: 4.0.1 (Apple Computer, Inc. build
|
255
|
+
# 5250)
|
256
|
+
#
|
257
|
+
|
258
|
+
def ffmpeg_build
|
259
|
+
/(\n\s*)(built on.*)(\n)/.match(@raw_response)[2]
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Returns the container format for the file. Instead of returning a single
|
264
|
+
# format, this may return a string of related formats.
|
265
|
+
#
|
266
|
+
# Examples:
|
267
|
+
#
|
268
|
+
# "avi"
|
269
|
+
#
|
270
|
+
# "mov,mp4,m4a,3gp,3g2,mj2"
|
271
|
+
#
|
272
|
+
|
273
|
+
def container
|
274
|
+
return nil if @unknown_format
|
275
|
+
|
276
|
+
/Input \#\d+\,\s*(\S+),\s*from/.match(@raw_metadata)[1]
|
277
|
+
end
|
278
|
+
|
279
|
+
#
|
280
|
+
# The duration of the movie, as a string.
|
281
|
+
#
|
282
|
+
# Example:
|
283
|
+
#
|
284
|
+
# "00:00:24.4" # 24.4 seconds
|
285
|
+
#
|
286
|
+
def raw_duration
|
287
|
+
return nil unless valid?
|
288
|
+
|
289
|
+
/Duration:\s*([0-9\:\.]+),/.match(@raw_metadata)[1]
|
290
|
+
end
|
291
|
+
|
292
|
+
#
|
293
|
+
# The duration of the movie in milliseconds, as an integer.
|
294
|
+
#
|
295
|
+
# Example:
|
296
|
+
#
|
297
|
+
# 24400 # 24.4 seconds
|
298
|
+
#
|
299
|
+
# Note that the precision of the duration is in tenths of a second, not
|
300
|
+
# thousandths, but milliseconds are a more standard unit of time than
|
301
|
+
# deciseconds.
|
302
|
+
#
|
303
|
+
|
304
|
+
def duration
|
305
|
+
return nil unless valid?
|
306
|
+
|
307
|
+
units = raw_duration.split(":")
|
308
|
+
(units[0].to_i * 60 * 60 * 1000) + (units[1].to_i * 60 * 1000) + (units[2].to_f * 1000).to_i
|
309
|
+
end
|
310
|
+
|
311
|
+
#
|
312
|
+
# The bitrate of the movie.
|
313
|
+
#
|
314
|
+
# Example:
|
315
|
+
#
|
316
|
+
# 3132
|
317
|
+
#
|
318
|
+
|
319
|
+
def bitrate
|
320
|
+
return nil unless valid?
|
321
|
+
|
322
|
+
bitrate_match[1].to_i
|
323
|
+
end
|
324
|
+
|
325
|
+
#
|
326
|
+
# The bitrate units used. In practice, this may always be kb/s.
|
327
|
+
#
|
328
|
+
# Example:
|
329
|
+
#
|
330
|
+
# "kb/s"
|
331
|
+
#
|
332
|
+
|
333
|
+
def bitrate_units
|
334
|
+
return nil unless valid?
|
335
|
+
|
336
|
+
bitrate_match[2]
|
337
|
+
end
|
338
|
+
|
339
|
+
def audio_bit_rate # :nodoc:
|
340
|
+
nil
|
341
|
+
end
|
342
|
+
|
343
|
+
def audio_stream
|
344
|
+
return nil unless valid?
|
345
|
+
|
346
|
+
#/\n\s*Stream.*Audio:.*\n/.match(@raw_response)[0].strip
|
347
|
+
match = /\n\s*Stream.*Audio:.*\n/.match(@raw_response)
|
348
|
+
return match[0].strip if match
|
349
|
+
end
|
350
|
+
|
351
|
+
#
|
352
|
+
# The audio codec used.
|
353
|
+
#
|
354
|
+
# Example:
|
355
|
+
#
|
356
|
+
# "aac"
|
357
|
+
#
|
358
|
+
|
359
|
+
def audio_codec
|
360
|
+
return nil unless audio?
|
361
|
+
|
362
|
+
audio_match[2]
|
363
|
+
end
|
364
|
+
|
365
|
+
#
|
366
|
+
# The sampling rate of the audio stream.
|
367
|
+
#
|
368
|
+
# Example:
|
369
|
+
#
|
370
|
+
# 44100
|
371
|
+
#
|
372
|
+
|
373
|
+
def audio_sample_rate
|
374
|
+
return nil unless audio?
|
375
|
+
|
376
|
+
audio_match[3].to_i
|
377
|
+
end
|
378
|
+
|
379
|
+
#
|
380
|
+
# The units used for the sampling rate. May always be Hz.
|
381
|
+
#
|
382
|
+
# Example:
|
383
|
+
#
|
384
|
+
# "Hz"
|
385
|
+
#
|
386
|
+
|
387
|
+
def audio_sample_units
|
388
|
+
return nil unless audio?
|
389
|
+
|
390
|
+
audio_match[4]
|
391
|
+
end
|
392
|
+
|
393
|
+
#
|
394
|
+
# The channels used in the audio stream.
|
395
|
+
#
|
396
|
+
# Examples:
|
397
|
+
# "stereo"
|
398
|
+
# "mono"
|
399
|
+
# "5:1"
|
400
|
+
#
|
401
|
+
|
402
|
+
def audio_channels_string
|
403
|
+
return nil unless audio?
|
404
|
+
|
405
|
+
audio_match[5]
|
406
|
+
end
|
407
|
+
|
408
|
+
def audio_channels
|
409
|
+
return nil unless audio?
|
410
|
+
|
411
|
+
case audio_match[5]
|
412
|
+
when "mono"
|
413
|
+
1
|
414
|
+
when "stereo"
|
415
|
+
2
|
416
|
+
when "5.1"
|
417
|
+
6
|
418
|
+
else
|
419
|
+
raise RuntimeError, "Unknown number of channels: #{audio_match[5]}"
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
#
|
424
|
+
# The ID of the audio stream (useful for troubleshooting).
|
425
|
+
#
|
426
|
+
# Example:
|
427
|
+
# #0.1
|
428
|
+
#
|
429
|
+
|
430
|
+
def audio_stream_id
|
431
|
+
return nil unless audio?
|
432
|
+
|
433
|
+
audio_match[1]
|
434
|
+
end
|
435
|
+
|
436
|
+
def video_stream
|
437
|
+
return nil unless valid?
|
438
|
+
|
439
|
+
match = /\n\s*Stream.*Video:.*\n/.match(@raw_response)
|
440
|
+
return match[0].strip unless match.nil?
|
441
|
+
nil
|
442
|
+
end
|
443
|
+
|
444
|
+
#
|
445
|
+
# The ID of the video stream (useful for troubleshooting).
|
446
|
+
#
|
447
|
+
# Example:
|
448
|
+
# #0.0
|
449
|
+
#
|
450
|
+
|
451
|
+
def video_stream_id
|
452
|
+
return nil unless video?
|
453
|
+
|
454
|
+
video_match[1]
|
455
|
+
end
|
456
|
+
|
457
|
+
#
|
458
|
+
# The video codec used.
|
459
|
+
#
|
460
|
+
# Example:
|
461
|
+
#
|
462
|
+
# "mpeg4"
|
463
|
+
#
|
464
|
+
|
465
|
+
def video_codec
|
466
|
+
return nil unless video?
|
467
|
+
|
468
|
+
video_match[2]
|
469
|
+
end
|
470
|
+
|
471
|
+
#
|
472
|
+
# The colorspace of the video stream.
|
473
|
+
#
|
474
|
+
# Example:
|
475
|
+
#
|
476
|
+
# "yuv420p"
|
477
|
+
#
|
478
|
+
|
479
|
+
def video_colorspace
|
480
|
+
return nil unless video?
|
481
|
+
|
482
|
+
video_match[3]
|
483
|
+
end
|
484
|
+
|
485
|
+
#
|
486
|
+
# The width of the video in pixels.
|
487
|
+
#
|
488
|
+
|
489
|
+
def width
|
490
|
+
return nil unless video?
|
491
|
+
|
492
|
+
video_match[4].to_i
|
493
|
+
end
|
494
|
+
|
495
|
+
#
|
496
|
+
# The height of the video in pixels.
|
497
|
+
#
|
498
|
+
|
499
|
+
def height
|
500
|
+
return nil unless video?
|
501
|
+
|
502
|
+
video_match[5].to_i
|
503
|
+
end
|
504
|
+
|
505
|
+
#
|
506
|
+
# width x height, as a string.
|
507
|
+
#
|
508
|
+
# Examples:
|
509
|
+
# 320x240
|
510
|
+
# 1280x720
|
511
|
+
#
|
512
|
+
|
513
|
+
def resolution
|
514
|
+
return nil unless video?
|
515
|
+
|
516
|
+
"#{width}x#{height}"
|
517
|
+
end
|
518
|
+
|
519
|
+
#
|
520
|
+
# The frame rate of the video in frames per second
|
521
|
+
#
|
522
|
+
# Example:
|
523
|
+
#
|
524
|
+
# "29.97"
|
525
|
+
#
|
526
|
+
|
527
|
+
def fps
|
528
|
+
return nil unless video?
|
529
|
+
|
530
|
+
/([0-9\.]+) (fps|tb)/.match(video_stream)[1]
|
531
|
+
end
|
532
|
+
|
533
|
+
private
|
534
|
+
|
535
|
+
def metadata_match
|
536
|
+
[
|
537
|
+
/(Input \#.*)\nMust/m, # ffmpeg
|
538
|
+
/(Input \#.*)\nAt least/m # ffmpeg macports
|
539
|
+
].each do |rgx|
|
540
|
+
if md=rgx.match(@raw_response)
|
541
|
+
return md
|
542
|
+
end
|
543
|
+
end
|
544
|
+
nil
|
545
|
+
end
|
546
|
+
|
547
|
+
def bitrate_match
|
548
|
+
/bitrate: ([0-9\.]+)\s*(.*)\s+/.match(@raw_metadata)
|
549
|
+
end
|
550
|
+
|
551
|
+
def audio_match
|
552
|
+
return nil unless valid?
|
553
|
+
|
554
|
+
/Stream\s*(.*?)[,|:|\(|\[].*?\s*Audio:\s*(.*?),\s*([0-9\.]*) (\w*),\s*([a-zA-Z:5\.1]*)/.match(audio_stream)
|
555
|
+
end
|
556
|
+
|
557
|
+
def video_match
|
558
|
+
return nil unless valid?
|
559
|
+
|
560
|
+
match = /Stream\s*(.*?)[,|:|\(|\[].*?\s*Video:\s*(.*?),\s*(.*?),\s*(\d*)x(\d*)/.match(video_stream)
|
561
|
+
|
562
|
+
# work-around for Apple Intermediate format, which does not have a color space
|
563
|
+
# I fake up a match data object (yea, duck typing!) with an empty spot where
|
564
|
+
# the color space would be.
|
565
|
+
if match.nil?
|
566
|
+
match = /Stream\s*(.*?)[,|:|\(|\[].*?\s*Video:\s*(.*?),\s*(\d*)x(\d*)/.match(video_stream)
|
567
|
+
match = [nil, match[1], match[2], nil, match[3], match[4]] unless match.nil?
|
568
|
+
end
|
569
|
+
|
570
|
+
match
|
571
|
+
end
|
572
|
+
end
|
573
|
+
end
|