mhs-rvideo 0.9.4 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +20 -0
- data/README.txt +3 -3
- data/lib/rvideo/errors.rb +3 -0
- data/lib/rvideo/inspector.rb +16 -2
- data/lib/rvideo/tools/abstract_tool.rb +236 -53
- data/lib/rvideo/tools/ffmpeg.rb +65 -11
- data/lib/rvideo/tools/ffmpeg2theora.rb +42 -0
- data/lib/rvideo/tools/flvtool2.rb +1 -0
- data/lib/rvideo/tools/mencoder.rb +49 -8
- data/lib/rvideo/tools/mp4creator.rb +6 -7
- data/lib/rvideo/tools/yamdi.rb +44 -0
- data/lib/rvideo/version.rb +1 -1
- data/lib/rvideo.rb +2 -0
- metadata +4 -2
data/History.txt
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
== 0.9.6 2010-01-04 (forked, mhs release)
|
2
|
+
|
3
|
+
* Updated RVideo::Inspect#capture_frame to raise RVideo::TranscoderError::OuptutFileNotFound error when ffmpeg fails to extract a screenshot from a given movie.
|
4
|
+
|
5
|
+
* Updated inspector to support ffmpeg output from the macports ffmpeg.
|
6
|
+
|
7
|
+
|
8
|
+
== 0.9.5 2008-09-09
|
9
|
+
Note:
|
10
|
+
* Moving hosting to GitHub.
|
11
|
+
http://github.com/zencoder/rvideo/tree
|
12
|
+
|
13
|
+
2 major enhancements:
|
14
|
+
* Large files, or some tools, would dump out so much output that it could run a server out of memory.
|
15
|
+
Changed the way that output is parsed. Instead of pulling it all into memory, the log file is sent to
|
16
|
+
disk, and then post-processed.
|
17
|
+
* Added support for yamdi (http://yamdi.sourceforge.net/) for FLV metadata injection. This is much faster
|
18
|
+
than flvtool2 on huge files, and uses less memory as well.
|
19
|
+
|
20
|
+
|
1
21
|
== 0.9.4 2007-12-12
|
2
22
|
|
3
23
|
3 major enhancements:
|
data/README.txt
CHANGED
@@ -82,10 +82,10 @@ do.
|
|
82
82
|
If RVideo is useful to you, you may also be interested in RMovie, another Ruby
|
83
83
|
video library. See http://rmovie.rubyforge.org/ for more.
|
84
84
|
|
85
|
-
Finally, watch for
|
86
|
-
Design.
|
85
|
+
Finally, watch for Zencoder, a commercial video transcoder built by Slantwise
|
86
|
+
Design. Zencoder uses RVideo for its video processing, but adds file queuing,
|
87
87
|
distributed transcoding, a web-based transcoder dashboard, and more. See
|
88
|
-
http://
|
88
|
+
http://zencoder.tv or http://slantwisedesign.com for more.
|
89
89
|
|
90
90
|
Copyright (c) 2007 Jonathan Dahl and Slantwise Design. Released under the MIT
|
91
91
|
license.
|
data/lib/rvideo/errors.rb
CHANGED
data/lib/rvideo/inspector.rb
CHANGED
@@ -161,6 +161,20 @@ module RVideo # :nodoc:
|
|
161
161
|
command = "ffmpeg -i #{@full_filename} -ss #{t} -t 00:00:01 -r 1 -vframes 1 -f image2 #{output_file}"
|
162
162
|
Transcoder.logger.info("\nCreating Screenshot: #{command}\n")
|
163
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
|
+
|
164
178
|
Transcoder.logger.info("\nScreenshot results: #{frame_result}")
|
165
179
|
output_file
|
166
180
|
end
|
@@ -322,7 +336,7 @@ module RVideo # :nodoc:
|
|
322
336
|
bitrate_match[2]
|
323
337
|
end
|
324
338
|
|
325
|
-
def
|
339
|
+
def audio_bit_rate # :nodoc:
|
326
340
|
nil
|
327
341
|
end
|
328
342
|
|
@@ -518,7 +532,7 @@ module RVideo # :nodoc:
|
|
518
532
|
|
519
533
|
def metadata_match
|
520
534
|
[
|
521
|
-
/(Input \#.*)\nMust/m, #
|
535
|
+
/(Input \#.*)\nMust/m, # ffmpeg
|
522
536
|
/(Input \#.*)\nAt least/m # ffmpeg macports
|
523
537
|
].each do |rgx|
|
524
538
|
if md=rgx.match(@raw_response)
|
@@ -11,8 +11,11 @@ module RVideo # :nodoc:
|
|
11
11
|
tool_name = cmd.split(" ").first
|
12
12
|
begin
|
13
13
|
tool = "RVideo::Tools::#{tool_name.classify}".constantize.send(:new, cmd, options)
|
14
|
-
rescue NameError, /uninitialized constant/
|
15
|
-
raise TranscoderError::UnknownTool, "The recipe tried to use the '#{tool_name}' tool, which does not exist."
|
14
|
+
# rescue NameError, /uninitialized constant/
|
15
|
+
# raise TranscoderError::UnknownTool, "The recipe tried to use the '#{tool_name}' tool, which does not exist."
|
16
|
+
rescue => e
|
17
|
+
LOGGER.info $!
|
18
|
+
LOGGER.info e.backtrace.join("\n")
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
@@ -30,86 +33,258 @@ module RVideo # :nodoc:
|
|
30
33
|
#
|
31
34
|
# Execute the command and parse the result.
|
32
35
|
#
|
33
|
-
|
36
|
+
# def execute
|
37
|
+
# @output_params = {}
|
38
|
+
# final_command = "#{@command} 2>&1"
|
39
|
+
# Transcoder.logger.info("\nExecuting Command: #{final_command}\n")
|
40
|
+
# @raw_result = `#{final_command}`
|
41
|
+
# Transcoder.logger.info("Result: \n#{@raw_result}")
|
42
|
+
# parse_result(@raw_result)
|
43
|
+
# end
|
44
|
+
|
34
45
|
def execute
|
35
|
-
|
46
|
+
@output_params = {}
|
47
|
+
|
48
|
+
# Dump the log output into a temp file
|
49
|
+
log_temp_file_name = "/tmp/transcode_output_#{Time.now.to_i}.txt"
|
50
|
+
|
51
|
+
final_command = "#{@command} 2>#{log_temp_file_name}"
|
36
52
|
Transcoder.logger.info("\nExecuting Command: #{final_command}\n")
|
37
|
-
|
53
|
+
`#{final_command}`
|
54
|
+
|
55
|
+
populate_raw_result(log_temp_file_name)
|
56
|
+
|
38
57
|
Transcoder.logger.info("Result: \n#{@raw_result}")
|
39
58
|
parse_result(@raw_result)
|
59
|
+
|
60
|
+
# Cleanup log file
|
61
|
+
begin
|
62
|
+
File.delete(log_temp_file_name)
|
63
|
+
rescue Exception => e
|
64
|
+
Transcoder.logger.error("Failed to delete output log file: #{log_temp_file_name}, e=#{e}")
|
65
|
+
end
|
40
66
|
end
|
41
67
|
|
42
68
|
#
|
43
69
|
# Magic parameters
|
44
70
|
#
|
71
|
+
def temp_dir
|
72
|
+
if @options['output_file']
|
73
|
+
"#{File.dirname(@options['output_file'])}/"
|
74
|
+
else
|
75
|
+
""
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
45
79
|
|
46
|
-
def
|
47
|
-
|
80
|
+
def fps
|
81
|
+
format_fps(get_fps)
|
48
82
|
end
|
49
83
|
|
50
|
-
def
|
51
|
-
if @
|
52
|
-
|
84
|
+
def get_fps
|
85
|
+
inspect_original if @original.nil?
|
86
|
+
fps = @options['fps'] || ""
|
87
|
+
case fps
|
88
|
+
when "copy"
|
89
|
+
get_original_fps
|
53
90
|
else
|
54
|
-
|
91
|
+
get_specific_fps
|
55
92
|
end
|
56
93
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
94
|
+
|
95
|
+
|
96
|
+
def resolution
|
97
|
+
format_resolution(get_resolution)
|
60
98
|
end
|
61
|
-
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
99
|
+
|
100
|
+
def get_resolution
|
101
|
+
inspect_original if @original.nil?
|
102
|
+
resolution_setting = @options['resolution'] || ""
|
103
|
+
case resolution_setting
|
104
|
+
when "copy"
|
105
|
+
get_original_resolution
|
106
|
+
when "width"
|
107
|
+
get_fit_to_width_resolution
|
108
|
+
when "height"
|
109
|
+
get_fit_to_height_resolution
|
110
|
+
when "letterbox"
|
111
|
+
get_letterbox_resolution
|
67
112
|
else
|
68
|
-
|
113
|
+
get_specific_resolution
|
69
114
|
end
|
70
115
|
end
|
116
|
+
|
117
|
+
|
118
|
+
def audio_channels
|
119
|
+
format_audio_channels(get_audio_channels)
|
120
|
+
end
|
71
121
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
122
|
+
def get_audio_channels
|
123
|
+
channels = @options['audio_channels'] || ""
|
124
|
+
case channels
|
125
|
+
when "stereo"
|
126
|
+
get_stereo_audio
|
127
|
+
when "mono"
|
128
|
+
get_mono_audio
|
129
|
+
else
|
130
|
+
{}
|
77
131
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
132
|
+
end
|
133
|
+
|
134
|
+
def audio_bit_rate
|
135
|
+
format_audio_bit_rate(get_audio_bit_rate)
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_audio_bit_rate
|
139
|
+
bit_rate = @options['audio_bit_rate'] || ""
|
140
|
+
case bit_rate
|
141
|
+
when ""
|
142
|
+
{}
|
87
143
|
else
|
88
|
-
|
144
|
+
get_specific_audio_bit_rate
|
89
145
|
end
|
90
146
|
end
|
91
|
-
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
147
|
+
|
148
|
+
def audio_sample_rate
|
149
|
+
format_audio_sample_rate(get_audio_sample_rate)
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_audio_sample_rate
|
153
|
+
sample_rate = @options['audio_sample_rate'] || ""
|
154
|
+
case sample_rate
|
155
|
+
when ""
|
156
|
+
{}
|
157
|
+
else
|
158
|
+
get_specific_audio_sample_rate
|
97
159
|
end
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
160
|
+
end
|
161
|
+
|
162
|
+
def video_quality
|
163
|
+
format_video_quality(get_video_quality)
|
164
|
+
end
|
165
|
+
|
166
|
+
def get_video_quality
|
167
|
+
inspect_original if @original.nil?
|
168
|
+
quality = @options['video_quality'] || 'medium'
|
169
|
+
video_bit_rate = @options['video_bit_rate'] || nil
|
170
|
+
h = {:video_quality => quality, :video_bit_rate => video_bit_rate}
|
171
|
+
h.merge!(get_fps).merge!(get_resolution)
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
def get_fit_to_width_resolution
|
177
|
+
w = @options['width']
|
178
|
+
raise TranscoderError::ParameterError, "invalid width of '#{w}' for fit to width" unless valid_dimension?(w)
|
179
|
+
h = calculate_height(@original.width, @original.height, w)
|
180
|
+
{:scale => {:width => w, :height => h}}
|
181
|
+
end
|
182
|
+
|
183
|
+
def get_fit_to_height_resolution
|
184
|
+
h = @options['height']
|
185
|
+
raise TranscoderError::ParameterError, "invalid height of '#{h}' for fit to height" unless valid_dimension?(h)
|
186
|
+
w = calculate_width(@original.width, @original.height, h)
|
187
|
+
{:scale => {:width => w, :height => h}}
|
188
|
+
end
|
189
|
+
|
190
|
+
def get_letterbox_resolution
|
191
|
+
lw = @options['width'].to_i
|
192
|
+
lh = @options['height'].to_i
|
193
|
+
raise TranscoderError::ParameterError, "invalid width of '#{lw}' for letterbox" unless valid_dimension?(lw)
|
194
|
+
raise TranscoderError::ParameterError, "invalid height of '#{lh}' for letterbox" unless valid_dimension?(lh)
|
195
|
+
w = calculate_width(@original.width, @original.height, lh)
|
196
|
+
h = calculate_height(@original.width, @original.height, lw)
|
197
|
+
if w > lw
|
198
|
+
w = lw
|
199
|
+
h = calculate_height(@original.width, @original.height, lw)
|
108
200
|
else
|
109
|
-
|
201
|
+
h = lh
|
202
|
+
w = calculate_width(@original.width, @original.height, lh)
|
110
203
|
end
|
204
|
+
{:scale => {:width => w, :height => h}, :letterbox => {:width => lw, :height => lh}}
|
205
|
+
end
|
206
|
+
|
207
|
+
def get_original_resolution
|
208
|
+
{:scale => {:width => @original.width, :height => @original.height}}
|
209
|
+
end
|
210
|
+
|
211
|
+
def get_specific_resolution
|
212
|
+
w = @options['width']
|
213
|
+
h = @options['height']
|
214
|
+
raise TranscoderError::ParameterError, "invalid width of '#{w}' for specific resolution" unless valid_dimension?(w)
|
215
|
+
raise TranscoderError::ParameterError, "invalid height of '#{h}' for specific resolution" unless valid_dimension?(h)
|
216
|
+
{:scale => {:width => w, :height => h}}
|
217
|
+
end
|
218
|
+
|
219
|
+
def get_original_fps
|
220
|
+
return {} if @original.fps.nil?
|
221
|
+
{:fps => @original.fps}
|
222
|
+
end
|
223
|
+
|
224
|
+
def get_specific_fps
|
225
|
+
{:fps => @options['fps']}
|
226
|
+
end
|
227
|
+
|
228
|
+
# def get_video_quality
|
229
|
+
# fps = @options['fps'] || @original.fps
|
230
|
+
# raise TranscoderError::ParameterError, "could not find fps in order to determine video quality" if fps.nil?
|
231
|
+
# width = @original.width
|
232
|
+
# height = @
|
233
|
+
# format_video_quality({:quality => @options['video_quality'], :bit_rate => @options['video_bit_rate']})
|
234
|
+
# end
|
235
|
+
|
236
|
+
def get_stereo_audio
|
237
|
+
{:channels => "2"}
|
238
|
+
end
|
239
|
+
|
240
|
+
def get_mono_audio
|
241
|
+
{:channels => "1"}
|
242
|
+
end
|
243
|
+
|
244
|
+
def get_specific_audio_bit_rate
|
245
|
+
{:bit_rate => @options['audio_bit_rate']}
|
246
|
+
end
|
247
|
+
|
248
|
+
def get_specific_audio_sample_rate
|
249
|
+
{:sample_rate => @options['audio_sample_rate']}
|
111
250
|
end
|
112
251
|
|
252
|
+
def calculate_width(ow, oh, h)
|
253
|
+
w = ((ow.to_f / oh.to_f) * h.to_f).to_i
|
254
|
+
(w.to_f / 16).round * 16
|
255
|
+
end
|
256
|
+
|
257
|
+
def calculate_height(ow, oh, w)
|
258
|
+
h = (w.to_f / (ow.to_f / oh.to_f)).to_i
|
259
|
+
(h.to_f / 16).round * 16
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
def valid_dimension?(dim)
|
264
|
+
return false if dim.to_i <= 0
|
265
|
+
return true
|
266
|
+
end
|
267
|
+
|
268
|
+
def format_resolution(params={})
|
269
|
+
raise ParameterError, "The #{self.class} tool has not implemented the format_resolution method."
|
270
|
+
end
|
271
|
+
|
272
|
+
def format_fps(params={})
|
273
|
+
raise ParameterError, "The #{self.class} tool has not implemented the format_fps method."
|
274
|
+
end
|
275
|
+
|
276
|
+
def format_audio_channels(params={})
|
277
|
+
raise ParameterError, "The #{self.class} tool has not implemented the format_audio_channels method."
|
278
|
+
end
|
279
|
+
|
280
|
+
def format_audio_bit_rate(params={})
|
281
|
+
raise ParameterError, "The #{self.class} tool has not implemented the format_audio_bit_rate method."
|
282
|
+
end
|
283
|
+
|
284
|
+
def format_audio_sample_rate(params={})
|
285
|
+
raise ParameterError, "The #{self.class} tool has not implemented the format_audio_sample_rate method."
|
286
|
+
end
|
287
|
+
|
113
288
|
private
|
114
289
|
|
115
290
|
|
@@ -123,6 +298,7 @@ module RVideo # :nodoc:
|
|
123
298
|
|
124
299
|
def interpolate_variables(raw_command)
|
125
300
|
raw_command.scan(/[^\\]\$[-_a-zA-Z]+\$/).each do |match|
|
301
|
+
match = match[0..0] == "$" ? match : match[1..(match.size - 1)]
|
126
302
|
match.strip!
|
127
303
|
raw_command.gsub!(match, matched_variable(match))
|
128
304
|
end
|
@@ -149,6 +325,13 @@ module RVideo # :nodoc:
|
|
149
325
|
def inspect_original
|
150
326
|
@original = Inspector.new(:file => options[:input_file])
|
151
327
|
end
|
328
|
+
|
329
|
+
# Pulls the interesting bits of the temp log file into memory. This is fairly tool-specific, so
|
330
|
+
# it's doubtful that this default version is going to work without being overridded.
|
331
|
+
def populate_raw_result(temp_file_name)
|
332
|
+
@raw_result = `tail -n 500 #{temp_file_name}`
|
333
|
+
end
|
334
|
+
|
152
335
|
end
|
153
336
|
|
154
337
|
end
|
data/lib/rvideo/tools/ffmpeg.rb
CHANGED
@@ -10,24 +10,78 @@ module RVideo
|
|
10
10
|
'ffmpeg'
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
13
|
+
def format_fps(params={})
|
14
|
+
" -r #{params[:fps]}"
|
15
|
+
end
|
16
|
+
def format_video_quality(params={})
|
17
|
+
bitrate = params[:video_bit_rate].blank? ? nil : params[:video_bit_rate]
|
18
|
+
factor = (params[:scale][:width].to_f * params[:scale][:height].to_f * params[:fps].to_f)
|
19
|
+
case params[:video_quality]
|
20
|
+
when 'low'
|
21
|
+
bitrate ||= (factor / 12000).to_i
|
22
|
+
" -v #{bitrate}k -crf 30 -me zero -subq 1 -refs 1 -threads auto "
|
23
|
+
when 'medium'
|
24
|
+
bitrate ||= (factor / 9000).to_i
|
25
|
+
" -v #{bitrate}k -crf 22 -flags +loop -cmp +sad -partitions +parti4x4+partp8x8+partb8x8 -flags2 +mixed_refs -me hex -subq 3 -trellis 1 -refs 2 -bf 3 -b_strategy 1 -coder 1 -me_range 16 -g 250"
|
26
|
+
when 'high'
|
27
|
+
bitrate ||= (factor / 3600).to_i
|
28
|
+
" -v #{bitrate}k -crf 18 -flags +loop -cmp +sad -partitions +parti4x4+partp8x8+partb8x8 -flags2 +mixed_refs -me full -subq 6 -trellis 1 -refs 3 -bf 3 -b_strategy 1 -coder 1 -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71"
|
24
29
|
else
|
25
30
|
""
|
26
31
|
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def format_resolution(params={})
|
36
|
+
p = " -s #{params[:scale][:width]}x#{params[:scale][:height]} "
|
37
|
+
if params[:letterbox]
|
38
|
+
plr = ((params[:letterbox][:width] - params[:scale][:width]) / 2).to_i
|
39
|
+
ptb = ((params[:letterbox][:height] - params[:scale][:height]) / 2).to_i
|
40
|
+
p += " -padtop #{ptb} -padbottom #{ptb} -padleft #{plr} -padright #{plr} "
|
41
|
+
end
|
42
|
+
p
|
43
|
+
end
|
44
|
+
|
45
|
+
def format_audio_channels(params={})
|
46
|
+
" -ac #{params[:channels]}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def format_audio_bit_rate(params={})
|
50
|
+
" -ab #{params[:bit_rate]}k"
|
27
51
|
end
|
28
52
|
|
53
|
+
def format_audio_sample_rate(params={})
|
54
|
+
" -ar #{params[:sample_rate]}"
|
55
|
+
end
|
56
|
+
|
57
|
+
|
29
58
|
private
|
30
59
|
|
60
|
+
# Turns the temp log file into a useful string, from which we can parse the
|
61
|
+
# transcoding results.
|
62
|
+
# These log files can be enormous, so pulling the whole thing into memory is not an
|
63
|
+
# option.
|
64
|
+
def populate_raw_result(temp_file_name)
|
65
|
+
@raw_result = ""
|
66
|
+
|
67
|
+
# Is the log file exceptionally long? It's really not a big deal to pull in a thousand lines or so
|
68
|
+
# into memory. It's the gigantic files that cause problems. If the files isn't too large,
|
69
|
+
# just pull it in.
|
70
|
+
line_count = 0
|
71
|
+
if m = /^\s*(\d+)/.match(`wc -l #{temp_file_name}`)
|
72
|
+
line_count = m[1].to_i
|
73
|
+
end
|
74
|
+
|
75
|
+
if line_count > 500
|
76
|
+
# Find the message indicating that the command is actually running.
|
77
|
+
running_string = "Press .* to stop encoding"
|
78
|
+
@raw_result << `grep "#{running_string}" #{temp_file_name}`
|
79
|
+
end
|
80
|
+
|
81
|
+
# Append the bottom of the log file, where the interesting bits live.
|
82
|
+
@raw_result << `tail -n 500 #{temp_file_name}`
|
83
|
+
end
|
84
|
+
|
31
85
|
def parse_result(result)
|
32
86
|
|
33
87
|
if m = /Unable for find a suitable output format for.*$/.match(result)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RVideo
|
2
|
+
module Tools
|
3
|
+
class Ffmpeg2theora
|
4
|
+
include AbstractTool::InstanceMethods
|
5
|
+
|
6
|
+
attr_reader :raw_metadata
|
7
|
+
|
8
|
+
def tool_command
|
9
|
+
'ffmpeg2theora'
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_video_quality(params={})
|
13
|
+
bitrate = params[:video_bit_rate].blank? ? nil : params[:video_bit_rate]
|
14
|
+
factor = (params[:scale][:width].to_f * params[:scale][:height].to_f * params[:fps].to_f)
|
15
|
+
case params[:video_quality]
|
16
|
+
when 'low'
|
17
|
+
" -v 1 "
|
18
|
+
when 'medium'
|
19
|
+
"-v 5 "
|
20
|
+
when 'high'
|
21
|
+
"-v 10 "
|
22
|
+
else
|
23
|
+
""
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_result(result)
|
28
|
+
if m = /does not exist or has an unknown data format/.match(result)
|
29
|
+
raise TranscoderError::InvalidFile, "I/O error"
|
30
|
+
end
|
31
|
+
|
32
|
+
if m = /General output options/.match(result)
|
33
|
+
raise TranscoderError::InvalidCommand, "no command passed to ffmpeg2theora, or no output file specified"
|
34
|
+
end
|
35
|
+
|
36
|
+
@raw_metadata = result.empty? ? "No Results" : result
|
37
|
+
return true
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -3,26 +3,67 @@ module RVideo
|
|
3
3
|
class Mencoder
|
4
4
|
include AbstractTool::InstanceMethods
|
5
5
|
|
6
|
-
attr_reader :frame, :size, :time, :bitrate, :video_size, :audio_size, :
|
7
|
-
|
6
|
+
attr_reader :frame, :size, :time, :bitrate, :video_size, :audio_size, :output_fps
|
7
|
+
|
8
8
|
def tool_command
|
9
9
|
'mencoder'
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def format_fps(params={})
|
13
|
+
" -ofps #{params[:fps]}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def format_resolution(params={})
|
17
|
+
p = " -vf scale=#{params[:scale][:width]}:#{params[:scale][:height]}"
|
18
|
+
if params[:letterbox]
|
19
|
+
p += ",expand=#{params[:letterbox][:width]}:#{params[:letterbox][:height]}"
|
20
|
+
end
|
21
|
+
p += ",harddup"
|
22
|
+
end
|
23
|
+
|
24
|
+
def format_audio_channels(params={})
|
25
|
+
" -channels #{params[:channels]}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def format_audio_bit_rate(params={})
|
29
|
+
" br=#{params[:bit_rate]}:"
|
30
|
+
end
|
31
|
+
|
32
|
+
def format_audio_sample_rate(params={})
|
33
|
+
" -srate #{params[:sample_rate]}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def format_video_quality(params={})
|
37
|
+
bitrate = params[:video_bit_rate].blank? ? nil : params[:video_bit_rate]
|
38
|
+
factor = (params[:scale][:width].to_f * params[:scale][:height].to_f * params[:fps].to_f)
|
39
|
+
case params[:video_quality]
|
40
|
+
when 'low'
|
41
|
+
bitrate ||= (factor / 12000).to_i
|
42
|
+
" -x264encopts threads=auto:subq=1:me=dia:frameref=1:crf=30:bitrate=#{bitrate} "
|
43
|
+
when 'medium'
|
44
|
+
bitrate ||= (factor / 9000).to_i
|
45
|
+
" -x264encopts threads=auto:subq=3:me=hex:frameref=2:crf=22:bitrate=#{bitrate} "
|
46
|
+
when 'high'
|
47
|
+
bitrate ||= (factor / 3600).to_i
|
48
|
+
" -x264encopts threads=auto:subq=6:me=dia:frameref=3:crf=18:bitrate=#{bitrate} "
|
16
49
|
else
|
17
50
|
""
|
18
51
|
end
|
19
|
-
end
|
52
|
+
end
|
53
|
+
|
20
54
|
|
55
|
+
|
56
|
+
private
|
57
|
+
|
21
58
|
def parse_result(result)
|
22
59
|
if m = /Exiting.*No output file specified/.match(result)
|
23
60
|
raise TranscoderError::InvalidCommand, "no command passed to mencoder, or no output file specified"
|
24
61
|
end
|
25
62
|
|
63
|
+
if m = /counldn't set specified parameters, exiting/.match(result)
|
64
|
+
raise TranscoderError::InvalidCommand, "a combination of the recipe parameters is invalid: #{result}"
|
65
|
+
end
|
66
|
+
|
26
67
|
if m = /Sorry, this file format is not recognized\/supported/.match(result)
|
27
68
|
raise TranscoderError::InvalidFile, "unknown format"
|
28
69
|
end
|
@@ -41,7 +82,7 @@ module RVideo
|
|
41
82
|
@video_size = sanitary_match(/size:\s*(\d*)\s*(\S*)/, video_details[0])
|
42
83
|
@time = sanitary_match(/bytes\s*([0-9.]*)/, video_details[0])
|
43
84
|
@frame = sanitary_match(/secs\s*(\d*)/, video_details[0])
|
44
|
-
@
|
85
|
+
@output_fps = (@frame.to_f / @time.to_f).round_to(3)
|
45
86
|
elsif result =~ /Video stream is mandatory/
|
46
87
|
raise TranscoderError::InvalidFile, "Video stream required, and no video stream found"
|
47
88
|
end
|
@@ -9,13 +9,8 @@ module RVideo
|
|
9
9
|
'mp4creator'
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
|
14
|
-
if @original.fps
|
15
|
-
"-rate=#{@original.fps}"
|
16
|
-
else
|
17
|
-
""
|
18
|
-
end
|
12
|
+
def format_fps(params={})
|
13
|
+
" -rate=#{params[:fps]}"
|
19
14
|
end
|
20
15
|
|
21
16
|
def parse_result(result)
|
@@ -27,6 +22,10 @@ module RVideo
|
|
27
22
|
raise TranscoderError::InvalidFile, "I/O error"
|
28
23
|
end
|
29
24
|
|
25
|
+
if @options['output_file'] && !File.exist?(@options['output_file'])
|
26
|
+
raise TranscoderError::UnexpectedResult, "An unknown error has occured with mp4creator:#{result}"
|
27
|
+
end
|
28
|
+
|
30
29
|
@raw_metadata = result.empty? ? "No Results" : result
|
31
30
|
return true
|
32
31
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RVideo
|
2
|
+
module Tools
|
3
|
+
class Yamdi
|
4
|
+
include AbstractTool::InstanceMethods
|
5
|
+
|
6
|
+
attr_reader :raw_metadata
|
7
|
+
|
8
|
+
def tool_command
|
9
|
+
'yamdi'
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def parse_result(result)
|
15
|
+
if result.empty?
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
|
19
|
+
if m = /Couldn't stat on (.*)/.match(result)
|
20
|
+
raise TranscoderError::InputFileNotFound, m[0]
|
21
|
+
end
|
22
|
+
|
23
|
+
if m = /The input file is not a FLV./.match(result)
|
24
|
+
raise TranscoderError::InvalidFile, "input must be a valid FLV file"
|
25
|
+
end
|
26
|
+
|
27
|
+
if m = /\(c\) \d{4} Ingo Oppermann/i.match(result)
|
28
|
+
raise TranscoderError::InvalidCommand, "command printed yamdi help text (and presumably didn't execute)"
|
29
|
+
end
|
30
|
+
|
31
|
+
if m = /Please provide at least one output file/i.match(result)
|
32
|
+
raise TranscoderError::InvalidCommand, "command did not contain a valid output file. Yamdi expects a -o switch."
|
33
|
+
end
|
34
|
+
|
35
|
+
if m = /ERROR: undefined method .?timestamp.? for nil/.match(result)
|
36
|
+
raise TranscoderError::InvalidFile, "Output file was empty (presumably)"
|
37
|
+
end
|
38
|
+
|
39
|
+
raise TranscoderError::UnexpectedResult, result
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/rvideo/version.rb
CHANGED
data/lib/rvideo.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mhs-rvideo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Dahl (Slantwise Design)
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-02-04 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -35,6 +35,8 @@ files:
|
|
35
35
|
- lib/rvideo/inspector.rb
|
36
36
|
- lib/rvideo/tools/abstract_tool.rb
|
37
37
|
- lib/rvideo/tools/ffmpeg.rb
|
38
|
+
- lib/rvideo/tools/ffmpeg2theora.rb
|
39
|
+
- lib/rvideo/tools/yamdi.rb
|
38
40
|
- lib/rvideo/tools/flvtool2.rb
|
39
41
|
- lib/rvideo/tools/mencoder.rb
|
40
42
|
- lib/rvideo/tools/mp4box.rb
|