newbamboo-rvideo 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +70 -0
- data/ENV +100 -0
- data/ENV2 +129 -0
- data/LICENSE +20 -0
- data/Manifest +68 -0
- data/README +106 -0
- data/RULES +11 -0
- data/Rakefile +63 -0
- data/config/boot.rb +25 -0
- data/lib/rvideo.rb +46 -0
- data/lib/rvideo/errors.rb +24 -0
- data/lib/rvideo/float.rb +7 -0
- data/lib/rvideo/frame_capturer.rb +127 -0
- data/lib/rvideo/inspector.rb +482 -0
- data/lib/rvideo/reporter.rb +176 -0
- data/lib/rvideo/reporter/views/index.html.erb +27 -0
- data/lib/rvideo/reporter/views/report.css +27 -0
- data/lib/rvideo/reporter/views/report.html.erb +81 -0
- data/lib/rvideo/reporter/views/report.js +9 -0
- data/lib/rvideo/string.rb +5 -0
- data/lib/rvideo/tools/abstract_tool.rb +406 -0
- data/lib/rvideo/tools/ffmpeg.rb +356 -0
- data/lib/rvideo/tools/ffmpeg2theora.rb +42 -0
- data/lib/rvideo/tools/flvtool2.rb +50 -0
- data/lib/rvideo/tools/mencoder.rb +103 -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/qtfaststart.rb +37 -0
- data/lib/rvideo/tools/segmenter.rb +21 -0
- data/lib/rvideo/tools/yamdi.rb +44 -0
- data/lib/rvideo/transcoder.rb +139 -0
- data/lib/rvideo/version.rb +9 -0
- data/rvideo.gemspec +36 -0
- data/scripts/txt2html +67 -0
- data/setup.rb +1585 -0
- data/spec/files/boat.avi +0 -0
- data/spec/files/kites.mp4 +0 -0
- data/spec/fixtures/ffmpeg_builds.yml +28 -0
- data/spec/fixtures/ffmpeg_results.yml +608 -0
- data/spec/fixtures/files.yml +398 -0
- data/spec/fixtures/recipes.yml +58 -0
- data/spec/integrations/formats_spec.rb +315 -0
- data/spec/integrations/frame_capturer_spec.rb +26 -0
- data/spec/integrations/inspection_spec.rb +112 -0
- data/spec/integrations/recipes_spec.rb +0 -0
- data/spec/integrations/rvideo_spec.rb +17 -0
- data/spec/integrations/transcoder_integration_spec.rb +29 -0
- data/spec/integrations/transcoding_spec.rb +9 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support.rb +36 -0
- data/spec/units/abstract_tool_spec.rb +111 -0
- data/spec/units/ffmpeg_spec.rb +323 -0
- data/spec/units/flvtool2_spec.rb +324 -0
- data/spec/units/frame_capturer_spec.rb +72 -0
- data/spec/units/inspector_spec.rb +59 -0
- data/spec/units/mencoder_spec.rb +4994 -0
- data/spec/units/mp4box_spec.rb +34 -0
- data/spec/units/mp4creator_spec.rb +34 -0
- data/spec/units/mplayer_spec.rb +34 -0
- data/spec/units/qtfaststart_spec.rb +35 -0
- data/spec/units/string_spec.rb +8 -0
- data/spec/units/transcoder_spec.rb +156 -0
- data/tasks/deployment.rake +5 -0
- data/tasks/testing.rake +27 -0
- data/tasks/transcoding.rake +40 -0
- data/tasks/website.rake +8 -0
- 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
|