newbamboo-rvideo 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/CHANGELOG +70 -0
  2. data/ENV +100 -0
  3. data/ENV2 +129 -0
  4. data/LICENSE +20 -0
  5. data/Manifest +68 -0
  6. data/README +106 -0
  7. data/RULES +11 -0
  8. data/Rakefile +63 -0
  9. data/config/boot.rb +25 -0
  10. data/lib/rvideo.rb +46 -0
  11. data/lib/rvideo/errors.rb +24 -0
  12. data/lib/rvideo/float.rb +7 -0
  13. data/lib/rvideo/frame_capturer.rb +127 -0
  14. data/lib/rvideo/inspector.rb +482 -0
  15. data/lib/rvideo/reporter.rb +176 -0
  16. data/lib/rvideo/reporter/views/index.html.erb +27 -0
  17. data/lib/rvideo/reporter/views/report.css +27 -0
  18. data/lib/rvideo/reporter/views/report.html.erb +81 -0
  19. data/lib/rvideo/reporter/views/report.js +9 -0
  20. data/lib/rvideo/string.rb +5 -0
  21. data/lib/rvideo/tools/abstract_tool.rb +406 -0
  22. data/lib/rvideo/tools/ffmpeg.rb +356 -0
  23. data/lib/rvideo/tools/ffmpeg2theora.rb +42 -0
  24. data/lib/rvideo/tools/flvtool2.rb +50 -0
  25. data/lib/rvideo/tools/mencoder.rb +103 -0
  26. data/lib/rvideo/tools/mp4box.rb +21 -0
  27. data/lib/rvideo/tools/mp4creator.rb +35 -0
  28. data/lib/rvideo/tools/mplayer.rb +31 -0
  29. data/lib/rvideo/tools/qtfaststart.rb +37 -0
  30. data/lib/rvideo/tools/segmenter.rb +21 -0
  31. data/lib/rvideo/tools/yamdi.rb +44 -0
  32. data/lib/rvideo/transcoder.rb +139 -0
  33. data/lib/rvideo/version.rb +9 -0
  34. data/rvideo.gemspec +36 -0
  35. data/scripts/txt2html +67 -0
  36. data/setup.rb +1585 -0
  37. data/spec/files/boat.avi +0 -0
  38. data/spec/files/kites.mp4 +0 -0
  39. data/spec/fixtures/ffmpeg_builds.yml +28 -0
  40. data/spec/fixtures/ffmpeg_results.yml +608 -0
  41. data/spec/fixtures/files.yml +398 -0
  42. data/spec/fixtures/recipes.yml +58 -0
  43. data/spec/integrations/formats_spec.rb +315 -0
  44. data/spec/integrations/frame_capturer_spec.rb +26 -0
  45. data/spec/integrations/inspection_spec.rb +112 -0
  46. data/spec/integrations/recipes_spec.rb +0 -0
  47. data/spec/integrations/rvideo_spec.rb +17 -0
  48. data/spec/integrations/transcoder_integration_spec.rb +29 -0
  49. data/spec/integrations/transcoding_spec.rb +9 -0
  50. data/spec/spec.opts +1 -0
  51. data/spec/spec_helper.rb +16 -0
  52. data/spec/support.rb +36 -0
  53. data/spec/units/abstract_tool_spec.rb +111 -0
  54. data/spec/units/ffmpeg_spec.rb +323 -0
  55. data/spec/units/flvtool2_spec.rb +324 -0
  56. data/spec/units/frame_capturer_spec.rb +72 -0
  57. data/spec/units/inspector_spec.rb +59 -0
  58. data/spec/units/mencoder_spec.rb +4994 -0
  59. data/spec/units/mp4box_spec.rb +34 -0
  60. data/spec/units/mp4creator_spec.rb +34 -0
  61. data/spec/units/mplayer_spec.rb +34 -0
  62. data/spec/units/qtfaststart_spec.rb +35 -0
  63. data/spec/units/string_spec.rb +8 -0
  64. data/spec/units/transcoder_spec.rb +156 -0
  65. data/tasks/deployment.rake +5 -0
  66. data/tasks/testing.rake +27 -0
  67. data/tasks/transcoding.rake +40 -0
  68. data/tasks/website.rake +8 -0
  69. metadata +179 -0
@@ -0,0 +1,356 @@
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, :pid
48
+
49
+ def initialize(raw_command, options = {})
50
+ @progress_sample_rate = options.delete(:progress_sample_rate)
51
+ @progress_timeout = options.delete(:progress_timeout)
52
+
53
+ @raw_command = raw_command
54
+ @options = HashWithIndifferentAccess.new(options)
55
+ @command = interpolate_variables(raw_command)
56
+ end
57
+
58
+ # Not sure if this is needed anymore...
59
+ def tool_command
60
+ 'ffmpeg'
61
+ end
62
+
63
+ def format_deinterlace(params={})
64
+ params[:deinterlace] ? "-deinterlace" : ""
65
+ end
66
+
67
+ def format_fps(params={})
68
+ "-r #{params[:fps]}"
69
+ end
70
+
71
+ def format_video_bit_rate(params = {})
72
+ "-#{video_bit_rate_parameter} #{params[:video_bit_rate]}k"
73
+ end
74
+
75
+ def format_video_bit_rate_tolerance(params = {})
76
+ "-bt #{params[:video_bit_rate_tolerance]}k"
77
+ end
78
+
79
+ def format_video_bit_rate_min(params = {})
80
+ "-minrate #{params[:video_bit_rate_min]}k"
81
+ end
82
+
83
+ def format_video_bit_rate_max(params = {})
84
+ "-maxrate #{params[:video_bit_rate_max]}k"
85
+ end
86
+
87
+ def format_video_quality(params={})
88
+ bitrate = params[:video_bit_rate].blank? ? nil : params[:video_bit_rate]
89
+
90
+ params.merge! get_original_fps unless params[:fps]
91
+
92
+ factor = params[:scale][:width].to_f * params[:scale][:height].to_f * params[:fps].to_f
93
+
94
+ case params[:video_quality]
95
+ when 'low'
96
+ bitrate ||= (factor / 12000).to_i
97
+ params[:video_bit_rate] = bitrate
98
+ "#{format_video_bit_rate params} -crf 30 -me zero -subq 1 -refs 1 -threads auto"
99
+ when 'medium'
100
+ bitrate ||= (factor / 9000).to_i
101
+ params[:video_bit_rate] = bitrate
102
+ "#{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"
103
+ when 'high'
104
+ bitrate ||= (factor / 3600).to_i
105
+ params[:video_bit_rate] = bitrate
106
+ "#{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"
107
+ end
108
+ end
109
+
110
+ def format_resolution(params={})
111
+ p = "-s #{params[:scale][:width]}x#{params[:scale][:height]}"
112
+ if params[:letterbox]
113
+ plr = ((params[:letterbox][:width] - params[:scale][:width]) / 2).to_i
114
+ ptb = ((params[:letterbox][:height] - params[:scale][:height]) / 2).to_i
115
+ p += " -padtop #{ptb} -padbottom #{ptb} -padleft #{plr} -padright #{plr} "
116
+ end
117
+ p
118
+ end
119
+
120
+ def format_audio_channels(params={})
121
+ "-ac #{params[:channels]}"
122
+ end
123
+
124
+ def format_audio_bit_rate(params={})
125
+ "-ab #{params[:bit_rate]}k"
126
+ end
127
+
128
+ def format_audio_sample_rate(params={})
129
+ "-ar #{params[:sample_rate]}"
130
+ end
131
+
132
+ def execute_with_progress
133
+ # TODO put progress_counter stuff back in because long videos might not update their progress for some time and we wouldn't want to accidently kill them!
134
+ final_command = @command
135
+ RVideo.logger.info("\nExecuting Command: #{final_command}\n")
136
+ @raw_result = ''
137
+ duration = 0
138
+ previous_line = nil
139
+ mutex = Mutex.new
140
+
141
+ command_thread = Thread.new do
142
+ @pid, stdin, stdout, stderr = Open4::popen4(final_command)
143
+ stderr.each("\r") do |line|
144
+ if line != previous_line
145
+ previous_line = line
146
+ new_progress, duration = parse_progress(line, duration)
147
+ mutex.synchronize do
148
+ @progress = new_progress
149
+ end
150
+ $defout.flush
151
+ end
152
+ # WARNING: we may need to set a limit to how many lines are appended to @raw_result as ffmpeg can spit out a massive amount of info if things go pear shaped
153
+ @raw_result += line + "\r"
154
+ end
155
+ end
156
+
157
+ # Yield the progress and monitor the process
158
+ current_timeout = 0
159
+ progress_to_yeild = nil
160
+
161
+ loop do
162
+ # Exit when the command is done
163
+ break unless command_thread.alive?
164
+ # Yield the progress
165
+ mutex.synchronize do
166
+ # First check if we're actually making progress! We should kill the command if it's not, as it's probably because ffmpeg has frozen
167
+ if progress_to_yeild == @progress
168
+ current_timeout += @progress_sample_rate
169
+ end
170
+ progress_to_yeild = @progress
171
+ end
172
+
173
+ unless @progress_timeout == false
174
+ self.kill if current_timeout >= @progress_timeout
175
+ end
176
+
177
+ yield progress_to_yeild
178
+ sleep @progress_sample_rate
179
+ end
180
+ end
181
+
182
+ def kill
183
+ Process.kill("SIGKILL", @pid)
184
+ raise TranscoderError, "Transcoder hung."
185
+ end
186
+
187
+ private
188
+
189
+ def parse_progress(line, duration)
190
+ if line =~ /Duration: (\d{2}):(\d{2}):(\d{2}).(\d{1})/
191
+ duration = (($1.to_i * 60 + $2.to_i) * 60 + $3.to_i) * 10 + $4.to_i
192
+ end
193
+
194
+ if line =~ /time=(\d+).(\d+)/
195
+ if not duration.nil? and duration != 0
196
+ p = ($1.to_i * 10 + $2.to_i) * 100 / duration
197
+ else
198
+ p = 0
199
+ end
200
+ p = 100 if p > 100
201
+ end
202
+ return [(p || nil), duration]
203
+ end
204
+
205
+ # Turns the temp log file into a useful string, from which we can parse the
206
+ # transcoding results.
207
+ # These log files can be enormous, so pulling the whole thing into memory is not an
208
+ # option.
209
+ def populate_raw_result(temp_file_name)
210
+ @raw_result = ""
211
+
212
+ # Is the log file exceptionally long? It's really not a big deal to pull in a thousand lines or so
213
+ # into memory. It's the gigantic files that cause problems. If the files isn't too large,
214
+ # just pull it in.
215
+ line_count = 0
216
+ if m = /^\s*(\d+)/.match(`wc -l #{temp_file_name}`)
217
+ line_count = m[1].to_i
218
+ end
219
+
220
+ if line_count > 500
221
+ # Find the message indicating that the command is actually running.
222
+ running_string = "Press .* to stop encoding"
223
+ @raw_result << `grep "#{running_string}" #{temp_file_name}`
224
+ end
225
+
226
+ # Append the bottom of the log file, where the interesting bits live.
227
+ @raw_result << `tail -n 500 #{temp_file_name}`
228
+ end
229
+
230
+ def parse_result(result)
231
+ if m = /Expected .+ for (.+) but found: (.+)/.match(result)
232
+ raise TranscoderError::InvalidCommand, m.to_s
233
+ end
234
+
235
+ if m = /Unable for find a suitable output format for.*$/.match(result)
236
+ raise TranscoderError::InvalidCommand, m[0]
237
+ end
238
+
239
+ if m = /Unknown codec \'(.*)\'/.match(result)
240
+ raise TranscoderError::InvalidFile, "Codec #{m[1]} not supported by this build of ffmpeg"
241
+ end
242
+
243
+ if m = /could not find codec parameters/.match(result)
244
+ raise TranscoderError::InvalidFile, "Codec not supported by this build of ffmpeg"
245
+ end
246
+
247
+ if m = /I\/O error occured\n(.*)$/.match(result)
248
+ raise TranscoderError::InvalidFile, "I/O error: #{m[1].strip}"
249
+ end
250
+
251
+ if m = /\n(.*)Unknown Format$/.match(result)
252
+ raise TranscoderError::InvalidFile, "unknown format (#{m[1]})"
253
+ end
254
+
255
+ if m = /\nERROR.*/m.match(result)
256
+ raise TranscoderError::InvalidFile, m[0]
257
+ end
258
+
259
+ if m = /[(\S+) @ \d+x\d+]error, (.+?)/.match(result)
260
+ raise TranscoderError::InvalidFile, "#{m[1]}: #{m[2]}"
261
+ end
262
+
263
+ if result =~ /usage: ffmpeg/
264
+ raise TranscoderError::InvalidCommand, "must pass a command to ffmpeg"
265
+ end
266
+
267
+ if result =~ /Output file does not contain.*stream/
268
+ raise TranscoderError, "Output file does not contain any video or audio streams."
269
+ end
270
+
271
+ if m = /Unsupported codec.*id=(.*)\).*for input stream\s*(.*)\s*/.match(result)
272
+ inspect_original if @original.nil?
273
+ case m[2]
274
+ when @original.audio_stream_id
275
+ codec_type = "audio"
276
+ codec = @original.audio_codec
277
+ when @original.video_stream_id
278
+ codec_type = "video"
279
+ codec = @original.video_codec
280
+ else
281
+ codec_type = "video or audio"
282
+ codec = "unknown"
283
+ end
284
+
285
+ raise TranscoderError::InvalidFile, "Unsupported #{codec_type} codec: #{codec} (id=#{m[1]}, stream=#{m[2]})"
286
+ #raise TranscoderError, "Codec #{m[1]} not supported (in stream #{m[2]})"
287
+ end
288
+
289
+ # Could not open './spec/../config/../tmp/processed/1/kites-1.avi'
290
+ if result =~ /Could not open .#{@output_file}.\Z/
291
+ raise TranscoderError, "Could not write output file to #{@output_file}"
292
+ end
293
+
294
+ full_details = /Press .* to stop encoding\n(.*)/m.match(result)
295
+ raise TranscoderError, "Unexpected result details (#{result})" if full_details.nil?
296
+ details = full_details[1].strip.gsub(/\s*\n\s*/," - ")
297
+
298
+ if details =~ /Could not write header/
299
+ raise TranscoderError, details
300
+ end
301
+
302
+ #frame= 584 q=6.0 Lsize= 708kB time=19.5 bitrate= 297.8kbits/s
303
+ #video:49kB audio:153kB global headers:0kB muxing overhead 250.444444%
304
+
305
+ #frame= 4126 q=31.0 Lsize= 5917kB time=69.1 bitrate= 702.0kbits/s
306
+ #video:2417kB audio:540kB global headers:0kB muxing overhead 100.140277%
307
+
308
+ #frame= 273 fps= 31 q=10.0 Lsize= 398kB time=5.9 bitrate= 551.8kbits/s
309
+ #video:284kB audio:92kB global headers:0kB muxing overhead 5.723981%
310
+
311
+ #mdb:94, lastbuf:0 skipping granule 0
312
+ #size= 1080kB time=69.1 bitrate= 128.0kbits /s
313
+ #video:0kB audio:1080kB global headers:0kB muxing overhead 0.002893%
314
+
315
+ #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%
316
+
317
+ #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
318
+
319
+ #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
320
+
321
+ #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
322
+
323
+ #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=
324
+
325
+ #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
326
+
327
+ # NOTE: had to remove "\s" from "\s.*L.*size=" from this regexp below.
328
+ # Not sure why. Unit tests were succeeding, but hand tests weren't.
329
+ if details =~ /video:/
330
+ #success = /^frame=\s*(\S*)\s*q=(\S*).*L.*size=\s*(\S*)\s*time=\s*(\S*)\s*bitrate=\s*(\S*)\s*/m.match(details)
331
+ @frame = details[/frame=\s*(\S*)/, 1]
332
+ @output_fps = details[/fps=\s*(\S*)/, 1]
333
+ @q = details[/\s+q=\s*(\S*)/, 1]
334
+ @size = details[/size=\s*(\S*)/, 1]
335
+ @time = details[/time=\s*(\S*)/, 1]
336
+ @output_bitrate = details[/bitrate=\s*(\S*)/, 1]
337
+
338
+ @video_size = details[/video:\s*(\S*)/, 1]
339
+ @audio_size = details[/audio:\s*(\S*)/, 1]
340
+ @header_size = details[/headers:\s*(\S*)/, 1]
341
+ @overhead = details[/overhead[:]*\s*(\S*)/, 1]
342
+
343
+ psnr_match = /PSNR=(.*)\s*size=/.match(details)
344
+ @psnr = psnr_match[1].strip if psnr_match
345
+ return true
346
+ end
347
+
348
+ #[mp3 @ 0x54340c]flv doesnt support that sample rate, choose from (44100, 22050, 11025)
349
+ #Could not write header for output file #0 (incorrect codec parameters ?)
350
+
351
+ raise TranscoderError::UnexpectedResult, details
352
+ end
353
+
354
+ end
355
+ end
356
+ 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
@@ -0,0 +1,50 @@
1
+ # Warning: If you're dealing with large files, you should consider using yamdi instead.
2
+ module RVideo
3
+ module Tools
4
+ class Flvtool2
5
+ include AbstractTool::InstanceMethods
6
+
7
+ attr_reader :raw_metadata
8
+
9
+ #attr_reader :has_key_frames, :cue_points, :audiodatarate, :has_video, :stereo, :can_seek_to_end, :framerate, :audiosamplerate, :videocodecid, :datasize, :lasttimestamp,
10
+ # :audiosamplesize, :audiosize, :has_audio, :audiodelay, :videosize, :metadatadate, :metadatacreator, :lastkeyframetimestamp, :height, :filesize, :has_metadata, :audiocodecid,
11
+ # :duration, :videodatarate, :has_cue_points, :width
12
+
13
+ def tool_command
14
+ 'flvtool2'
15
+ end
16
+
17
+ private
18
+
19
+ def parse_result(result)
20
+ if result.empty?
21
+ return true
22
+ end
23
+
24
+ if m = /ERROR: No such file or directory(.*)\n/.match(result)
25
+ raise TranscoderError::InputFileNotFound, m[0]
26
+ end
27
+
28
+ if m = /ERROR: IO is not a FLV stream/.match(result)
29
+ raise TranscoderError::InvalidFile, "input must be a valid FLV file"
30
+ end
31
+
32
+ if m = /Copyright.*Norman Timmler/i.match(result)
33
+ raise TranscoderError::InvalidCommand, "command printed flvtool2 help text (and presumably didn't execute)"
34
+ end
35
+
36
+ if m = /ERROR: undefined method .?timestamp.? for nil/.match(result)
37
+ raise TranscoderError::InvalidFile, "Output file was empty (presumably)"
38
+ end
39
+
40
+ if m = /\A---(.*)...\Z/m.match(result)
41
+ @raw_metadata = m[0]
42
+ return true
43
+ end
44
+
45
+ raise TranscoderError::UnexpectedResult, result
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,103 @@
1
+ module RVideo
2
+ module Tools
3
+ class Mencoder
4
+ include AbstractTool::InstanceMethods
5
+
6
+ attr_reader :frame, :size, :time, :bitrate, :video_size, :audio_size, :output_fps
7
+
8
+ def tool_command
9
+ 'mencoder'
10
+ end
11
+
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} "
49
+ else
50
+ ""
51
+ end
52
+ end
53
+
54
+
55
+
56
+ private
57
+
58
+ def parse_result(result)
59
+ if m = /Exiting.*No output file specified/.match(result)
60
+ raise TranscoderError::InvalidCommand, "no command passed to mencoder, or no output file specified"
61
+ end
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
+
67
+ if m = /Sorry, this file format is not recognized\/supported/.match(result)
68
+ raise TranscoderError::InvalidFile, "unknown format"
69
+ end
70
+
71
+ if m = /Cannot open file\/device./.match(result)
72
+ raise TranscoderError::InvalidFile, "I/O error"
73
+ end
74
+
75
+ if m = /File not found:$/.match(result)
76
+ raise TranscoderError::InvalidFile, "I/O error"
77
+ end
78
+
79
+ video_details = result.match /Video stream:(.*)$/
80
+ if video_details
81
+ @bitrate = video_details[0][/Video stream:\s*([0-9.]*)/, 1]
82
+ @video_size = video_details[0][/size:\s*(\d*)\s*(\S*)/, 1]
83
+ @time = video_details[0][/bytes\s*([0-9.]*)/, 1]
84
+ @frame = video_details[0][/secs\s*(\d*)/, 1]
85
+ @output_fps = (@frame.to_f / @time.to_f).round_to(3)
86
+
87
+ elsif result =~ /Video stream is mandatory/
88
+ raise TranscoderError::InvalidFile,
89
+ "Video stream required, and no video stream found"
90
+ end
91
+
92
+ audio_details = result.match /Audio stream:(.*)$/
93
+ if audio_details
94
+ @audio_size = audio_details[0][/size:\s*(\d*)\s*\S*/, 1]
95
+ else
96
+ @audio_size = 0
97
+ end
98
+ @size = (@video_size.to_i + @audio_size.to_i).to_s
99
+ end
100
+
101
+ end
102
+ end
103
+ end