axtro-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.
- data/CHANGELOG +70 -0
- data/ENV +100 -0
- data/ENV2 +129 -0
- data/LICENSE +20 -0
- data/Manifest +67 -0
- data/README +91 -0
- data/RULES +11 -0
- data/Rakefile +63 -0
- data/axtro-rvideo.gemspec +36 -0
- data/config/boot.rb +25 -0
- data/lib/rvideo.rb +44 -0
- data/lib/rvideo/errors.rb +24 -0
- data/lib/rvideo/float.rb +7 -0
- data/lib/rvideo/frame_capturer.rb +129 -0
- data/lib/rvideo/inspector.rb +483 -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 +414 -0
- data/lib/rvideo/tools/ffmpeg.rb +286 -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/yamdi.rb +44 -0
- data/lib/rvideo/transcoder.rb +120 -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 +123 -0
- data/spec/units/ffmpeg_spec.rb +327 -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 +178 -0
|
@@ -0,0 +1,286 @@
|
|
|
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_deinterlace(params={})
|
|
55
|
+
params[:deinterlace] ? "-deinterlace" : ""
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def format_fps(params={})
|
|
59
|
+
"-r #{params[:fps]}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def format_video_bit_rate(params = {})
|
|
63
|
+
"-#{video_bit_rate_parameter} #{params[:video_bit_rate]}k"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def format_video_bit_rate_tolerance(params = {})
|
|
67
|
+
"-bt #{params[:video_bit_rate_tolerance]}k"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def format_video_bit_rate_min(params = {})
|
|
71
|
+
"-minrate #{params[:video_bit_rate_min]}k"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def format_video_bit_rate_max(params = {})
|
|
75
|
+
"-maxrate #{params[:video_bit_rate_max]}k"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def format_video_quality(params={})
|
|
79
|
+
bitrate = params[:video_bit_rate].blank? ? nil : params[:video_bit_rate]
|
|
80
|
+
|
|
81
|
+
params.merge! get_original_fps unless params[:fps]
|
|
82
|
+
|
|
83
|
+
factor = params[:scale][:width].to_f * params[:scale][:height].to_f * params[:fps].to_f
|
|
84
|
+
|
|
85
|
+
case params[:video_quality]
|
|
86
|
+
when 'low'
|
|
87
|
+
bitrate ||= (factor / 12000).to_i
|
|
88
|
+
params[:video_bit_rate] = bitrate
|
|
89
|
+
"#{format_video_bit_rate params} -crf 30 -me zero -subq 1 -refs 1 -threads auto"
|
|
90
|
+
when 'medium'
|
|
91
|
+
bitrate ||= (factor / 9000).to_i
|
|
92
|
+
params[:video_bit_rate] = bitrate
|
|
93
|
+
"#{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"
|
|
94
|
+
when 'high'
|
|
95
|
+
bitrate ||= (factor / 3600).to_i
|
|
96
|
+
params[:video_bit_rate] = bitrate
|
|
97
|
+
"#{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"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def format_resolution(params={})
|
|
102
|
+
p = "-s #{params[:scale][:width]}x#{params[:scale][:height]}"
|
|
103
|
+
if params[:letterbox]
|
|
104
|
+
plr = ((params[:letterbox][:width] - params[:scale][:width]) / 2).to_i
|
|
105
|
+
ptb = ((params[:letterbox][:height] - params[:scale][:height]) / 2).to_i
|
|
106
|
+
p += " -padtop #{ptb} -padbottom #{ptb} -padleft #{plr} -padright #{plr} "
|
|
107
|
+
end
|
|
108
|
+
p
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def format_audio_channels(params={})
|
|
112
|
+
"-ac #{params[:channels]}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def format_audio_bit_rate(params={})
|
|
116
|
+
"-ab #{params[:bit_rate]}k"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def format_audio_sample_rate(params={})
|
|
120
|
+
"-ar #{params[:sample_rate]}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def get_resolution
|
|
124
|
+
if @options['resolution'] && @options['resolution'].match(/(\d+)x(\d+)/)
|
|
125
|
+
@options['width'] = $1
|
|
126
|
+
@options['height'] = $2
|
|
127
|
+
get_specific_resolution
|
|
128
|
+
else
|
|
129
|
+
super
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
# Turns the temp log file into a useful string, from which we can parse the
|
|
136
|
+
# transcoding results.
|
|
137
|
+
# These log files can be enormous, so pulling the whole thing into memory is not an
|
|
138
|
+
# option.
|
|
139
|
+
def populate_raw_result(temp_file_name)
|
|
140
|
+
@raw_result = ""
|
|
141
|
+
|
|
142
|
+
# Is the log file exceptionally long? It's really not a big deal to pull in a thousand lines or so
|
|
143
|
+
# into memory. It's the gigantic files that cause problems. If the files isn't too large,
|
|
144
|
+
# just pull it in.
|
|
145
|
+
line_count = 0
|
|
146
|
+
if m = /^\s*(\d+)/.match(`wc -l #{temp_file_name}`)
|
|
147
|
+
line_count = m[1].to_i
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
if line_count > 500
|
|
151
|
+
# Find the message indicating that the command is actually running.
|
|
152
|
+
running_string = "Press .* to stop encoding"
|
|
153
|
+
@raw_result << `grep "#{running_string}" #{temp_file_name}`
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Append the bottom of the log file, where the interesting bits live.
|
|
157
|
+
@raw_result << `tail -n 500 #{temp_file_name}`
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def parse_result(result)
|
|
161
|
+
if m = /Expected .+ for (.+) but found: (.+)/.match(result)
|
|
162
|
+
raise TranscoderError::InvalidCommand, m.to_s
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if m = /Unable for find a suitable output format for.*$/.match(result)
|
|
166
|
+
raise TranscoderError::InvalidCommand, m[0]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if m = /Unknown codec \'(.*)\'/.match(result)
|
|
170
|
+
raise TranscoderError::InvalidFile, "Codec #{m[1]} not supported by this build of ffmpeg"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if m = /could not find codec parameters/.match(result)
|
|
174
|
+
raise TranscoderError::InvalidFile, "Codec not supported by this build of ffmpeg"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
if m = /I\/O error occured\n(.*)$/.match(result)
|
|
178
|
+
raise TranscoderError::InvalidFile, "I/O error: #{m[1].strip}"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
if m = /\n(.*)Unknown Format$/.match(result)
|
|
182
|
+
raise TranscoderError::InvalidFile, "unknown format (#{m[1]})"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
if m = /\nERROR.*/m.match(result)
|
|
186
|
+
raise TranscoderError::InvalidFile, m[0]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
if m = /[(\S+) @ \d+x\d+]error, (.+?)/.match(result)
|
|
190
|
+
raise TranscoderError::InvalidFile, "#{m[1]}: #{m[2]}"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
if result =~ /usage: ffmpeg/
|
|
194
|
+
raise TranscoderError::InvalidCommand, "must pass a command to ffmpeg"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
if result =~ /Output file does not contain.*stream/
|
|
198
|
+
raise TranscoderError, "Output file does not contain any video or audio streams."
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if m = /Unsupported codec.*id=(.*)\).*for input stream\s*(.*)\s*/.match(result)
|
|
202
|
+
inspect_original if @original.nil?
|
|
203
|
+
case m[2]
|
|
204
|
+
when @original.audio_stream_id
|
|
205
|
+
codec_type = "audio"
|
|
206
|
+
codec = @original.audio_codec
|
|
207
|
+
when @original.video_stream_id
|
|
208
|
+
codec_type = "video"
|
|
209
|
+
codec = @original.video_codec
|
|
210
|
+
else
|
|
211
|
+
codec_type = "video or audio"
|
|
212
|
+
codec = "unknown"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
raise TranscoderError::InvalidFile, "Unsupported #{codec_type} codec: #{codec} (id=#{m[1]}, stream=#{m[2]})"
|
|
216
|
+
#raise TranscoderError, "Codec #{m[1]} not supported (in stream #{m[2]})"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Could not open './spec/../config/../tmp/processed/1/kites-1.avi'
|
|
220
|
+
if result =~ /Could not open .#{@output_file}.\Z/
|
|
221
|
+
raise TranscoderError, "Could not write output file to #{@output_file}"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
full_details = /Press .* to stop encoding\n(.*)/m.match(result)
|
|
225
|
+
raise TranscoderError, "Unexpected result details (#{result})" if full_details.nil?
|
|
226
|
+
details = full_details[1].strip.gsub(/\s*\n\s*/," - ")
|
|
227
|
+
|
|
228
|
+
if details =~ /Could not write header/
|
|
229
|
+
raise TranscoderError, details
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
#frame= 584 q=6.0 Lsize= 708kB time=19.5 bitrate= 297.8kbits/s
|
|
233
|
+
#video:49kB audio:153kB global headers:0kB muxing overhead 250.444444%
|
|
234
|
+
|
|
235
|
+
#frame= 4126 q=31.0 Lsize= 5917kB time=69.1 bitrate= 702.0kbits/s
|
|
236
|
+
#video:2417kB audio:540kB global headers:0kB muxing overhead 100.140277%
|
|
237
|
+
|
|
238
|
+
#frame= 273 fps= 31 q=10.0 Lsize= 398kB time=5.9 bitrate= 551.8kbits/s
|
|
239
|
+
#video:284kB audio:92kB global headers:0kB muxing overhead 5.723981%
|
|
240
|
+
|
|
241
|
+
#mdb:94, lastbuf:0 skipping granule 0
|
|
242
|
+
#size= 1080kB time=69.1 bitrate= 128.0kbits /s
|
|
243
|
+
#video:0kB audio:1080kB global headers:0kB muxing overhead 0.002893%
|
|
244
|
+
|
|
245
|
+
#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%
|
|
246
|
+
|
|
247
|
+
#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
|
|
248
|
+
|
|
249
|
+
#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
|
|
250
|
+
|
|
251
|
+
#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
|
|
252
|
+
|
|
253
|
+
#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=
|
|
254
|
+
|
|
255
|
+
#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
|
|
256
|
+
|
|
257
|
+
# NOTE: had to remove "\s" from "\s.*L.*size=" from this regexp below.
|
|
258
|
+
# Not sure why. Unit tests were succeeding, but hand tests weren't.
|
|
259
|
+
if details =~ /video:/
|
|
260
|
+
#success = /^frame=\s*(\S*)\s*q=(\S*).*L.*size=\s*(\S*)\s*time=\s*(\S*)\s*bitrate=\s*(\S*)\s*/m.match(details)
|
|
261
|
+
@frame = details[/frame=\s*(\S*)/, 1]
|
|
262
|
+
@output_fps = details[/fps=\s*(\S*)/, 1]
|
|
263
|
+
@q = details[/\s+q=\s*(\S*)/, 1]
|
|
264
|
+
@size = details[/size=\s*(\S*)/, 1]
|
|
265
|
+
@time = details[/time=\s*(\S*)/, 1]
|
|
266
|
+
@output_bitrate = details[/bitrate=\s*(\S*)/, 1]
|
|
267
|
+
|
|
268
|
+
@video_size = details[/video:\s*(\S*)/, 1]
|
|
269
|
+
@audio_size = details[/audio:\s*(\S*)/, 1]
|
|
270
|
+
@header_size = details[/headers:\s*(\S*)/, 1]
|
|
271
|
+
@overhead = details[/overhead[:]*\s*(\S*)/, 1]
|
|
272
|
+
|
|
273
|
+
psnr_match = /PSNR=(.*)\s*size=/.match(details)
|
|
274
|
+
@psnr = psnr_match[1].strip if psnr_match
|
|
275
|
+
return true
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
#[mp3 @ 0x54340c]flv doesnt support that sample rate, choose from (44100, 22050, 11025)
|
|
279
|
+
#Could not write header for output file #0 (incorrect codec parameters ?)
|
|
280
|
+
|
|
281
|
+
raise TranscoderError::UnexpectedResult, details
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
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
|