kakra-rvideo 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,393 @@
1
+ module RVideo # :nodoc:
2
+ module Tools # :nodoc:
3
+
4
+ # AbstractTool is an interface to every transcoder tool class
5
+ # (e.g. ffmpeg, flvtool2). Called by the Transcoder class.
6
+ class AbstractTool
7
+
8
+ def self.assign(cmd, options = {})
9
+ tool_name = cmd.split(" ").first
10
+ begin
11
+ tool = "RVideo::Tools::#{tool_name.classify}".constantize.send(:new, cmd, options)
12
+ # rescue NameError, /uninitialized constant/
13
+ # raise TranscoderError::UnknownTool, "The recipe tried to use the '#{tool_name}' tool, which does not exist."
14
+ rescue => e
15
+ RVideo.logger.info e.message
16
+ RVideo.logger.info e.backtrace.join("\n")
17
+ raise e
18
+ end
19
+ end
20
+
21
+
22
+ module InstanceMethods
23
+ # Defines abstract methods in the convention of "format_#{attribute}"
24
+ # which are meant to be redefined by classes including this behavior.
25
+ def self.abstract_attribute_formatter(*names)
26
+ names.map { |n| "format_#{n}" }.each do |name|
27
+ class_eval %{
28
+ def #{name}(params = {})
29
+ raise ParameterError,
30
+ "The #{self.class} tool has not implemented the :#{name} method."
31
+ end
32
+ }, __FILE__, __LINE__
33
+ end
34
+ end
35
+
36
+ abstract_attribute_formatter :resolution, :fps,
37
+ :video_bit_rate, :video_bit_rate_tolerance,
38
+ :video_bit_rate_min, :video_bit_rate_max,
39
+ :audio_channels, :audio_bit_rate, :audio_sample_rate
40
+
41
+ ###
42
+
43
+ attr_reader :options, :command, :raw_result
44
+ attr_writer :original
45
+
46
+ def initialize(raw_command, options = {})
47
+ @raw_command = raw_command
48
+ @options = HashWithIndifferentAccess.new(options)
49
+ @command = interpolate_variables(raw_command)
50
+ end
51
+
52
+ def execute
53
+ @output_params = {}
54
+
55
+ # Dump the log output into a temp file
56
+ log_temp_file_name = "/tmp/transcode_output_#{Time.now.to_i}.txt"
57
+
58
+ final_command = "#{@command} 2>#{log_temp_file_name}"
59
+ RVideo.logger.info("\nExecuting Command: #{final_command}\n")
60
+ do_execute final_command
61
+
62
+ populate_raw_result(log_temp_file_name)
63
+
64
+ RVideo.logger.info("Result: \n#{@raw_result}")
65
+ parse_result(@raw_result)
66
+
67
+ # Cleanup log file
68
+ begin
69
+ File.delete(log_temp_file_name)
70
+ rescue Exception => e
71
+ RVideo.logger.error("Failed to delete output log file: #{log_temp_file_name}, e=#{e}")
72
+ end
73
+ end
74
+
75
+ # Wrapper around the system call, for whenever we need to
76
+ # hook on or redefine this without messing with Kernel
77
+ def do_execute(command)
78
+ system command
79
+ end
80
+
81
+ #
82
+ # Magic parameters
83
+ #
84
+ def temp_dir
85
+ if @options['output_file']
86
+ "#{File.dirname(@options['output_file'])}/"
87
+ else
88
+ ""
89
+ end
90
+ end
91
+
92
+ ###
93
+ # FPS aka framerate
94
+
95
+ def fps
96
+ format_fps(get_fps)
97
+ end
98
+
99
+ def get_fps
100
+ inspect_original if @original.nil?
101
+ fps = @options['fps'] || ""
102
+ case fps
103
+ when "copy"
104
+ get_original_fps
105
+ else
106
+ get_specific_fps
107
+ end
108
+ end
109
+
110
+ def get_original_fps
111
+ return {} if @original.fps.nil?
112
+ { :fps => @original.fps }
113
+ end
114
+
115
+ def get_specific_fps
116
+ { :fps => @options['fps'] }
117
+ end
118
+
119
+ ###
120
+ # Resolution
121
+
122
+ def resolution
123
+ format_resolution(get_resolution)
124
+ end
125
+
126
+ def get_resolution
127
+ inspect_original if @original.nil?
128
+
129
+ case @options['resolution']
130
+ when "copy" then get_original_resolution
131
+ when "width" then get_fit_to_width_resolution
132
+ when "height" then get_fit_to_height_resolution
133
+ when "letterbox" then get_letterbox_resolution
134
+ else
135
+ if @options["width"] and not @options["height"]
136
+ get_fit_to_width_resolution
137
+ elsif @options["height"] and not @options["width"]
138
+ get_fit_to_height_resolution
139
+ elsif @options["width"] and @options["height"]
140
+ get_specific_resolution
141
+ else
142
+ get_original_resolution
143
+ end
144
+ end
145
+ end
146
+
147
+ def get_fit_to_width_resolution
148
+ w = @options['width']
149
+
150
+ raise TranscoderError::ParameterError,
151
+ "invalid width of '#{w}' for fit to width" unless valid_dimension?(w)
152
+
153
+ h = calculate_height(@original.width, @original.height, w)
154
+
155
+ { :scale => { :width => w, :height => h } }
156
+ end
157
+
158
+ def get_fit_to_height_resolution
159
+ h = @options['height']
160
+
161
+ raise TranscoderError::ParameterError,
162
+ "invalid height of '#{h}' for fit to height" unless valid_dimension?(h)
163
+
164
+ w = calculate_width(@original.width, @original.height, h)
165
+
166
+ { :scale => { :width => w, :height => h } }
167
+ end
168
+
169
+ def get_letterbox_resolution
170
+ lw = @options['width'].to_i
171
+ lh = @options['height'].to_i
172
+
173
+ raise TranscoderError::ParameterError,
174
+ "invalid width of '#{lw}' for letterbox" unless valid_dimension?(lw)
175
+ raise TranscoderError::ParameterError,
176
+ "invalid height of '#{lh}' for letterbox" unless valid_dimension?(lh)
177
+
178
+ w = calculate_width(@original.width, @original.height, lh)
179
+ h = calculate_height(@original.width, @original.height, lw)
180
+
181
+ if w > lw
182
+ w = lw
183
+ h = calculate_height(@original.width, @original.height, lw)
184
+ else
185
+ h = lh
186
+ w = calculate_width(@original.width, @original.height, lh)
187
+ end
188
+
189
+ { :scale => { :width => w, :height => h },
190
+ :letterbox => { :width => lw, :height => lh } }
191
+ end
192
+
193
+ def get_original_resolution
194
+ { :scale => { :width => @original.width, :height => @original.height } }
195
+ end
196
+
197
+ def get_specific_resolution
198
+ w = @options['width']
199
+ h = @options['height']
200
+
201
+ raise TranscoderError::ParameterError,
202
+ "invalid width of '#{w}' for specific resolution" unless valid_dimension?(w)
203
+ raise TranscoderError::ParameterError,
204
+ "invalid height of '#{h}' for specific resolution" unless valid_dimension?(h)
205
+
206
+ { :scale => { :width => w, :height => h } }
207
+ end
208
+
209
+ def calculate_width(ow, oh, h)
210
+ w = ((ow.to_f / oh.to_f) * h.to_f).to_i
211
+ (w.to_f / 16).round * 16
212
+ end
213
+
214
+ def calculate_height(ow, oh, w)
215
+ h = (w.to_f / (ow.to_f / oh.to_f)).to_i
216
+ (h.to_f / 16).round * 16
217
+ end
218
+
219
+ def valid_dimension?(dim)
220
+ dim.to_i > 0
221
+ end
222
+
223
+ ###
224
+ # Audio channels
225
+
226
+ def audio_channels
227
+ format_audio_channels(get_audio_channels)
228
+ end
229
+
230
+ def get_audio_channels
231
+ channels = @options['audio_channels'] || ""
232
+ case channels
233
+ when "stereo"
234
+ get_stereo_audio
235
+ when "mono"
236
+ get_mono_audio
237
+ else
238
+ {}
239
+ end
240
+ end
241
+
242
+ def get_stereo_audio
243
+ { :channels => "2" }
244
+ end
245
+
246
+ def get_mono_audio
247
+ { :channels => "1" }
248
+ end
249
+
250
+ def get_specific_audio_bit_rate
251
+ { :bit_rate => @options['audio_bit_rate'] }
252
+ end
253
+
254
+ def get_specific_audio_sample_rate
255
+ { :sample_rate => @options['audio_sample_rate'] }
256
+ end
257
+
258
+ ###
259
+ # Audio bit rate
260
+
261
+ def audio_bit_rate
262
+ format_audio_bit_rate(get_audio_bit_rate)
263
+ end
264
+
265
+ def get_audio_bit_rate
266
+ bit_rate = @options['audio_bit_rate'] || ""
267
+ case bit_rate
268
+ when ""
269
+ {}
270
+ else
271
+ get_specific_audio_bit_rate
272
+ end
273
+ end
274
+
275
+ ###
276
+ # Audio sample rate
277
+
278
+ def audio_sample_rate
279
+ format_audio_sample_rate(get_audio_sample_rate)
280
+ end
281
+
282
+ def get_audio_sample_rate
283
+ sample_rate = @options['audio_sample_rate'] || ""
284
+ case sample_rate
285
+ when ""
286
+ {}
287
+ else
288
+ get_specific_audio_sample_rate
289
+ end
290
+ end
291
+
292
+ ###
293
+ # Video quality
294
+
295
+ def video_quality
296
+ format_video_quality(get_video_quality)
297
+ end
298
+
299
+ def get_video_quality
300
+ quality = @options['video_quality'] || 'medium'
301
+
302
+ { :video_quality => quality }.
303
+ merge!(get_fps).
304
+ merge!(get_resolution).
305
+ merge!(get_video_bit_rate)
306
+ end
307
+
308
+ def video_bit_rate
309
+ format_video_bit_rate(get_video_bit_rate)
310
+ end
311
+
312
+ def get_video_bit_rate
313
+ { :video_bit_rate => @options["video_bit_rate"] }
314
+ end
315
+
316
+ def video_bit_rate_tolerance
317
+ format_video_bit_rate_tolerance(get_video_bit_rate_tolerance)
318
+ end
319
+
320
+ def get_video_bit_rate_tolerance
321
+ { :video_bit_rate_tolerance => @options["video_bit_rate_tolerance"] }
322
+ end
323
+
324
+ def video_bit_rate_min
325
+ format_video_bit_rate_min(get_video_bit_rate_min)
326
+ end
327
+
328
+ def get_video_bit_rate_min
329
+ { :video_bit_rate_min => @options["video_bit_rate_min"] }
330
+ end
331
+
332
+ def video_bit_rate_max
333
+ format_video_bit_rate_max(get_video_bit_rate_max)
334
+ end
335
+
336
+ def get_video_bit_rate_max
337
+ { :video_bit_rate_max => @options["video_bit_rate_max"] }
338
+ end
339
+
340
+ private
341
+
342
+ VARIABLE_INTERPOLATION_SCAN_PATTERN = /[^\\]\$[-_a-zA-Z]+\$/
343
+
344
+ def interpolate_variables(raw_command)
345
+ raw_command.scan(VARIABLE_INTERPOLATION_SCAN_PATTERN).each do |match|
346
+ match = match[0..0] == "$" ? match : match[1..(match.size - 1)]
347
+ match.strip!
348
+
349
+ value = if ["$input_file$", "$output_file$"].include?(match)
350
+ matched_variable(match).to_s.shell_quoted
351
+ else
352
+ matched_variable(match).to_s
353
+ end
354
+
355
+ raw_command.gsub!(match, value)
356
+ end
357
+ raw_command.gsub("\\$", "$")
358
+ end
359
+
360
+ #
361
+ # Strip the $s. First, look for a supplied option that matches the
362
+ # variable name. If one is not found, look for a method that matches.
363
+ # If not found, raise ParameterError exception.
364
+ #
365
+
366
+ def matched_variable(match)
367
+ variable_name = match.gsub("$","")
368
+ if self.respond_to? variable_name
369
+ self.send(variable_name)
370
+ elsif @options.key?(variable_name)
371
+ @options[variable_name]
372
+ else
373
+ raise TranscoderError::ParameterError,
374
+ "command is looking for the #{variable_name} parameter, but it was not provided. (Command: #{@raw_command})"
375
+ end
376
+ end
377
+
378
+
379
+ def inspect_original
380
+ @original = Inspector.new(:file => options[:input_file])
381
+ end
382
+
383
+ # Pulls the interesting bits of the temp log file into memory. This is fairly tool-specific, so
384
+ # it's doubtful that this default version is going to work without being overridded.
385
+ def populate_raw_result(temp_file_name)
386
+ @raw_result = `tail -n 500 #{temp_file_name}`
387
+ end
388
+
389
+ end # InstanceMethods
390
+ end
391
+
392
+ end
393
+ end
@@ -0,0 +1,269 @@
1
+ module RVideo
2
+ module Tools
3
+ class Ffmpeg
4
+ RESOLUTION_ABBREVIATIONS = {
5
+ "sqcif" => "128x96",
6
+ "qcif" => "176x144",
7
+ "cif" => "352x288",
8
+ "4cif" => "704x576",
9
+ "qqvga" => "160x120",
10
+ "qvga" => "320x240",
11
+ "vga" => "640x480",
12
+ "svga" => "800x600",
13
+ "xga" => "1024x768",
14
+ "uxga" => "1600x1200",
15
+ "qxga" => "2048x1536",
16
+ "sxga" => "1280x1024",
17
+ "qsxga" => "2560x2048",
18
+ "hsxga" => "5120x4096",
19
+ "wvga" => "852x480",
20
+ "wxga" => "1366x768",
21
+ "wsxga" => "1600x1024",
22
+ "wuxga" => "1920x1200",
23
+ "woxga" => "2560x1600",
24
+ "wqsxga" => "3200x2048",
25
+ "wquxga" => "3840x2400",
26
+ "whsxga" => "6400x4096",
27
+ "whuxga" => "7680x4800",
28
+ "cga" => "320x200",
29
+ "ega" => "640x350",
30
+ "hd480" => "852x480",
31
+ "hd720" => "1280x720",
32
+ "hd1080" => "1920x1080"
33
+ }
34
+
35
+ VALID_ASPECT_STRINGS = ["4:3", "16:9"]
36
+ VALID_ASPECT_FLOATS = [1.3333, 1.7777]
37
+
38
+ # The flag used to set video bitrate has apparently changed between
39
+ # different ffmpeg versions. In the latest builds -b is used.
40
+ # In older builds it was -v which is now used to set verbosity of logging.
41
+ DEFAULT_VIDEO_BIT_RATE_PARAMETER = "b"
42
+ cattr_accessor :video_bit_rate_parameter
43
+ self.video_bit_rate_parameter = DEFAULT_VIDEO_BIT_RATE_PARAMETER
44
+
45
+ include AbstractTool::InstanceMethods
46
+
47
+ attr_reader :frame, :q, :size, :time, :output_bitrate, :video_size, :audio_size, :header_size, :overhead, :psnr, :output_fps
48
+
49
+ # Not sure if this is needed anymore...
50
+ def tool_command
51
+ 'ffmpeg'
52
+ end
53
+
54
+ def format_fps(params={})
55
+ "-r #{params[:fps]}"
56
+ end
57
+
58
+ def format_video_bit_rate(params = {})
59
+ "-#{video_bit_rate_parameter} #{params[:video_bit_rate]}k"
60
+ end
61
+
62
+ def format_video_bit_rate_tolerance(params = {})
63
+ "-bt #{params[:video_bit_rate_tolerance]}k"
64
+ end
65
+
66
+ def format_video_bit_rate_min(params = {})
67
+ "-minrate #{params[:video_bit_rate_min]}k"
68
+ end
69
+
70
+ def format_video_bit_rate_max(params = {})
71
+ "-maxrate #{params[:video_bit_rate_max]}k"
72
+ end
73
+
74
+ def format_video_quality(params={})
75
+ bitrate = params[:video_bit_rate].blank? ? nil : params[:video_bit_rate]
76
+
77
+ params.merge! get_original_fps unless params[:fps]
78
+
79
+ factor = params[:scale][:width].to_f * params[:scale][:height].to_f * params[:fps].to_f
80
+
81
+ case params[:video_quality]
82
+ when 'low'
83
+ bitrate ||= (factor / 12000).to_i
84
+ params[:video_bit_rate] = bitrate
85
+ "#{format_video_bit_rate params} -crf 30 -me zero -subq 1 -refs 1 -threads auto"
86
+ when 'medium'
87
+ bitrate ||= (factor / 9000).to_i
88
+ params[:video_bit_rate] = bitrate
89
+ "#{format_video_bit_rate params} -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"
90
+ when 'high'
91
+ bitrate ||= (factor / 3600).to_i
92
+ params[:video_bit_rate] = bitrate
93
+ "#{format_video_bit_rate params} -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"
94
+ end
95
+ end
96
+
97
+ def format_resolution(params={})
98
+ p = "-s #{params[:scale][:width]}x#{params[:scale][:height]}"
99
+ if params[:letterbox]
100
+ plr = ((params[:letterbox][:width] - params[:scale][:width]) / 2).to_i
101
+ ptb = ((params[:letterbox][:height] - params[:scale][:height]) / 2).to_i
102
+ p += " -padtop #{ptb} -padbottom #{ptb} -padleft #{plr} -padright #{plr} "
103
+ end
104
+ p
105
+ end
106
+
107
+ def format_audio_channels(params={})
108
+ "-ac #{params[:channels]}"
109
+ end
110
+
111
+ def format_audio_bit_rate(params={})
112
+ "-ab #{params[:bit_rate]}k"
113
+ end
114
+
115
+ def format_audio_sample_rate(params={})
116
+ "-ar #{params[:sample_rate]}"
117
+ end
118
+
119
+
120
+ private
121
+
122
+ # Turns the temp log file into a useful string, from which we can parse the
123
+ # transcoding results.
124
+ # These log files can be enormous, so pulling the whole thing into memory is not an
125
+ # option.
126
+ def populate_raw_result(temp_file_name)
127
+ @raw_result = ""
128
+
129
+ # Is the log file exceptionally long? It's really not a big deal to pull in a thousand lines or so
130
+ # into memory. It's the gigantic files that cause problems. If the files isn't too large,
131
+ # just pull it in.
132
+ line_count = 0
133
+ if m = /^\s*(\d+)/.match(`wc -l #{temp_file_name}`)
134
+ line_count = m[1].to_i
135
+ end
136
+
137
+ if line_count > 500
138
+ # Find the message indicating that the command is actually running.
139
+ running_string = "Press .* to stop encoding"
140
+ @raw_result << `grep "#{running_string}" #{temp_file_name}`
141
+ end
142
+
143
+ # Append the bottom of the log file, where the interesting bits live.
144
+ @raw_result << `tail -n 500 #{temp_file_name}`
145
+ end
146
+
147
+ def parse_result(result)
148
+ if m = /Expected .+ for (.+) but found: (.+)/.match(result)
149
+ raise TranscoderError::InvalidCommand, m.to_s
150
+ end
151
+
152
+ if m = /Unable for find a suitable output format for.*$/.match(result)
153
+ raise TranscoderError::InvalidCommand, m[0]
154
+ end
155
+
156
+ if m = /Unknown codec \'(.*)\'/.match(result)
157
+ raise TranscoderError::InvalidFile, "Codec #{m[1]} not supported by this build of ffmpeg"
158
+ end
159
+
160
+ if m = /could not find codec parameters/.match(result)
161
+ raise TranscoderError::InvalidFile, "Codec not supported by this build of ffmpeg"
162
+ end
163
+
164
+ if m = /I\/O error occured\n(.*)$/.match(result)
165
+ raise TranscoderError::InvalidFile, "I/O error: #{m[1].strip}"
166
+ end
167
+
168
+ if m = /\n(.*)Unknown Format$/.match(result)
169
+ raise TranscoderError::InvalidFile, "unknown format (#{m[1]})"
170
+ end
171
+
172
+ if m = /\nERROR.*/m.match(result)
173
+ raise TranscoderError::InvalidFile, m[0]
174
+ end
175
+
176
+ if result =~ /usage: ffmpeg/
177
+ raise TranscoderError::InvalidCommand, "must pass a command to ffmpeg"
178
+ end
179
+
180
+ if result =~ /Output file does not contain.*stream/
181
+ raise TranscoderError, "Output file does not contain any video or audio streams."
182
+ end
183
+
184
+ if m = /Unsupported codec.*id=(.*)\).*for input stream\s*(.*)\s*/.match(result)
185
+ inspect_original if @original.nil?
186
+ case m[2]
187
+ when @original.audio_stream_id
188
+ codec_type = "audio"
189
+ codec = @original.audio_codec
190
+ when @original.video_stream_id
191
+ codec_type = "video"
192
+ codec = @original.video_codec
193
+ else
194
+ codec_type = "video or audio"
195
+ codec = "unknown"
196
+ end
197
+
198
+ raise TranscoderError::InvalidFile, "Unsupported #{codec_type} codec: #{codec} (id=#{m[1]}, stream=#{m[2]})"
199
+ #raise TranscoderError, "Codec #{m[1]} not supported (in stream #{m[2]})"
200
+ end
201
+
202
+ # Could not open './spec/../config/../tmp/processed/1/kites-1.avi'
203
+ if result =~ /Could not open .#{@output_file}.\Z/
204
+ raise TranscoderError, "Could not write output file to #{@output_file}"
205
+ end
206
+
207
+ full_details = /Press .* to stop encoding\n(.*)/m.match(result)
208
+ raise TranscoderError, "Unexpected result details (#{result})" if full_details.nil?
209
+ details = full_details[1].strip.gsub(/\s*\n\s*/," - ")
210
+
211
+ if details =~ /Could not write header/
212
+ raise TranscoderError, details
213
+ end
214
+
215
+ #frame= 584 q=6.0 Lsize= 708kB time=19.5 bitrate= 297.8kbits/s
216
+ #video:49kB audio:153kB global headers:0kB muxing overhead 250.444444%
217
+
218
+ #frame= 4126 q=31.0 Lsize= 5917kB time=69.1 bitrate= 702.0kbits/s
219
+ #video:2417kB audio:540kB global headers:0kB muxing overhead 100.140277%
220
+
221
+ #frame= 273 fps= 31 q=10.0 Lsize= 398kB time=5.9 bitrate= 551.8kbits/s
222
+ #video:284kB audio:92kB global headers:0kB muxing overhead 5.723981%
223
+
224
+ #mdb:94, lastbuf:0 skipping granule 0
225
+ #size= 1080kB time=69.1 bitrate= 128.0kbits /s
226
+ #video:0kB audio:1080kB global headers:0kB muxing overhead 0.002893%
227
+
228
+ #size= 80kB time=5.1 bitrate= 128.0kbits/s ^Msize= 162kB time=10.3 bitrate= 128.0kbits/s ^Msize= 241kB time=15.4 bitrate= 128.0kbits/s ^Msize= 329kB time=21.1 bitrate= 128.0kbits/s ^Msize= 413kB time=26.4 bitrate= 128.0kbits/s ^Msize= 506kB time=32.4 bitrate= 128.0kbits/s ^Msize= 591kB time=37.8 bitrate= 128.0kbits/s ^Msize= 674kB time=43.2 bitrate= 128.0kbits/s ^Msize= 771kB time=49.4 bitrate= 128.0kbits/s ^Msize= 851kB time=54.5 bitrate= 128.0kbits/s ^Msize= 932kB time=59.6 bitrate= 128.0kbits/s ^Msize= 1015kB time=64.9 bitrate= 128.0kbits/s ^Msize= 1094kB time=70.0 bitrate= 128.0kbits/s ^Msize= 1175kB time=75.2 bitrate= 128.0kbits/s ^Msize= 1244kB time=79.6 bitrate= 128.0kbits/s ^Msize= 1335kB time=85.4 bitrate= 128.0kbits/s ^Msize= 1417kB time=90.7 bitrate= 128.0kbits/s ^Msize= 1508kB time=96.5 bitrate= 128.0kbits/s ^Msize= 1589kB time=101.7 bitrate= 128.0kbits/s ^Msize= 1671kB time=106.9 bitrate= 128.0kbits/s ^Msize= 1711kB time=109.5 bitrate= 128.0kbits/s - video:0kB audio:1711kB global headers:0kB muxing overhead 0.001826%
229
+
230
+ #mdb:14, lastbuf:0 skipping granule 0 - overread, skip -5 enddists: -2 -2 - overread, skip -5 enddists: -2 -2 - size= 90kB time=5.7 bitrate= 128.0kbits/s \nsize= 189kB time=12.1 bitrate= 128.0kbits/s
231
+
232
+ #size= 59kB time=20.2 bitrate= 24.0kbits/s \nsize= 139kB time=47.4 bitrate= 24.0kbits/s \nsize= 224kB time=76.5 bitrate= 24.0kbits/s \nsize= 304kB time=103.7 bitrate= 24.0kbits/s \nsi
233
+
234
+ #mdb:14, lastbuf:0 skipping granule 0 - overread, skip -5 enddists: -2 -2 - overread, skip -5 enddists: -2 -2 - size= 81kB time=10.3 bitrate= 64.0kbits/s \nsize= 153kB time=19.6 bitrate= 64.0kbits/s
235
+
236
+ #size= 65kB time=4.1 bitrate= 128.1kbits/s \nsize= 119kB time=7.6 bitrate= 128.0kbits/s \nsize= 188kB time=12.0 bitrate= 128.0kbits/s \nsize= 268kB time=17.1 bitrate= 128.0kbits/s \nsize=
237
+
238
+ #Error while decoding stream #0.1 [mpeg4aac @ 0xb7d089f0]faac: frame decoding failed: Gain control not yet implementedError while decoding stream #0.1frame= 2143 fps= 83 q=4.0 size= 4476kB time=71.3 bitrate= 514.5kbits/s ^M[mpeg4aac @ 0xb7d089f0]faac: frame decoding failed: Gain control not yet implementedError while decoding stream #0.1
239
+
240
+ # NOTE: had to remove "\s" from "\s.*L.*size=" from this regexp below.
241
+ # Not sure why. Unit tests were succeeding, but hand tests weren't.
242
+ if details =~ /video:/
243
+ #success = /^frame=\s*(\S*)\s*q=(\S*).*L.*size=\s*(\S*)\s*time=\s*(\S*)\s*bitrate=\s*(\S*)\s*/m.match(details)
244
+ @frame = details[/frame=\s*(\S*)/, 1]
245
+ @output_fps = details[/fps=\s*(\S*)/, 1]
246
+ @q = details[/\s+q=\s*(\S*)/, 1]
247
+ @size = details[/size=\s*(\S*)/, 1]
248
+ @time = details[/time=\s*(\S*)/, 1]
249
+ @output_bitrate = details[/bitrate=\s*(\S*)/, 1]
250
+
251
+ @video_size = details[/video:\s*(\S*)/, 1]
252
+ @audio_size = details[/audio:\s*(\S*)/, 1]
253
+ @header_size = details[/headers:\s*(\S*)/, 1]
254
+ @overhead = details[/overhead[:]*\s*(\S*)/, 1]
255
+
256
+ psnr_match = /PSNR=(.*)\s*size=/.match(details)
257
+ @psnr = psnr_match[1].strip if psnr_match
258
+ return true
259
+ end
260
+
261
+ #[mp3 @ 0x54340c]flv doesnt support that sample rate, choose from (44100, 22050, 11025)
262
+ #Could not write header for output file #0 (incorrect codec parameters ?)
263
+
264
+ raise TranscoderError::UnexpectedResult, details
265
+ end
266
+
267
+ end
268
+ end
269
+ end
@@ -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