axtro-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 +67 -0
  6. data/README +91 -0
  7. data/RULES +11 -0
  8. data/Rakefile +63 -0
  9. data/axtro-rvideo.gemspec +36 -0
  10. data/config/boot.rb +25 -0
  11. data/lib/rvideo.rb +44 -0
  12. data/lib/rvideo/errors.rb +24 -0
  13. data/lib/rvideo/float.rb +7 -0
  14. data/lib/rvideo/frame_capturer.rb +129 -0
  15. data/lib/rvideo/inspector.rb +483 -0
  16. data/lib/rvideo/reporter.rb +176 -0
  17. data/lib/rvideo/reporter/views/index.html.erb +27 -0
  18. data/lib/rvideo/reporter/views/report.css +27 -0
  19. data/lib/rvideo/reporter/views/report.html.erb +81 -0
  20. data/lib/rvideo/reporter/views/report.js +9 -0
  21. data/lib/rvideo/string.rb +5 -0
  22. data/lib/rvideo/tools/abstract_tool.rb +414 -0
  23. data/lib/rvideo/tools/ffmpeg.rb +286 -0
  24. data/lib/rvideo/tools/ffmpeg2theora.rb +42 -0
  25. data/lib/rvideo/tools/flvtool2.rb +50 -0
  26. data/lib/rvideo/tools/mencoder.rb +103 -0
  27. data/lib/rvideo/tools/mp4box.rb +21 -0
  28. data/lib/rvideo/tools/mp4creator.rb +35 -0
  29. data/lib/rvideo/tools/mplayer.rb +31 -0
  30. data/lib/rvideo/tools/qtfaststart.rb +37 -0
  31. data/lib/rvideo/tools/yamdi.rb +44 -0
  32. data/lib/rvideo/transcoder.rb +120 -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 +123 -0
  54. data/spec/units/ffmpeg_spec.rb +327 -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 +178 -0
@@ -0,0 +1,176 @@
1
+ require 'erb'
2
+
3
+ module RVideo
4
+ class Reporter
5
+ include ERB::Util
6
+
7
+ def self.run
8
+ Reporter.new.run
9
+ end
10
+
11
+ def run
12
+ @current_report_path = Reporter.next_available_report_path(File.join(REPORT_PATH, 'generated_reports'))
13
+ files = available_files
14
+ recipes = available_recipes
15
+
16
+ puts "\nInput files:\n--#{files.collect { |file| File.basename(file) }.join("\n--")}"
17
+ puts "\nInput recipes:\n--#{recipes.map {|name, recipe| name }.join("\n--")}"
18
+ combinations = calculate_combinations_using recipes, files
19
+ results = mass_transcode combinations
20
+ build_report_from results
21
+ puts "Done! Report available at #{@current_report_path}"
22
+ puts "Launching report in browser..."
23
+ exec "open #{@current_report_path}/index.html"
24
+ end
25
+
26
+ private
27
+
28
+ def self.next_available_report_path(base_path)
29
+ ordered_reports = Dir[File.join(base_path, "*")].sort_by {|name| File.basename(name).to_i }
30
+ ordered_reports = ["0"] if ordered_reports.empty?
31
+ last_report = File.basename(ordered_reports.last)
32
+ new_report_name = (last_report.to_i + 1).to_s
33
+ new_dir = File.join(base_path, new_report_name)
34
+ FileUtils.mkdir_p(new_dir)
35
+ new_dir
36
+ end
37
+
38
+ def available_recipes
39
+ recipes = []
40
+ recipe_files = Dir[File.join(REPORT_PATH, "*.yml")]
41
+ recipe_files.each do |recipe_file|
42
+ YAML.load_file(recipe_file).each { |recipe| recipes << recipe }
43
+ end
44
+ if recipes.empty?
45
+ puts "No recipes found. Add recipe YAML files to report/."
46
+ exit
47
+ else
48
+ recipes
49
+ end
50
+ end
51
+
52
+ def available_files
53
+ files = Dir[File.join(REPORT_PATH, "files/input/*.*")]
54
+ if files.empty?
55
+ puts "No input files. Add files to report/files/input to test."
56
+ exit
57
+ else
58
+ files
59
+ end
60
+ end
61
+
62
+ def calculate_combinations_using(recipes, files)
63
+ @combinations = {}
64
+ files.each { |file| @combinations[file] = recipes }
65
+ @combinations
66
+ end
67
+
68
+ def build_report_from(results, options = nil)
69
+ @results = results
70
+ #build main report
71
+ report = load_view 'index'
72
+ full_report_path = File.join(@current_report_path, "index.html")
73
+ File.open(full_report_path, "w+") do |file|
74
+ file.write report
75
+ end
76
+ #build individual reports
77
+ @results.each do |input_file, recipes|
78
+ recipes.each do |recipe_name, result|
79
+ build_individual_report(input_file, recipe_name, result)
80
+ end
81
+ end
82
+ end
83
+
84
+ def build_individual_report(input_file, recipe_name, result)
85
+ #instance variables may no longer be necessary...
86
+ @input_file = input_file
87
+ @recipe_name = recipe_name
88
+ @result = result
89
+ individual_report = load_view 'report'
90
+ individual_report_name = "#{underscoreize_file_basename(input_file)}_#{recipe_name}.html"
91
+ File.makedirs(File.join(@current_report_path, "individual_reports"))
92
+ full_report_path = File.join(@current_report_path, "individual_reports", individual_report_name)
93
+ File.open(full_report_path, "w+") do |file|
94
+ file.write individual_report
95
+ end
96
+ end
97
+
98
+
99
+ def load_view(template_name)
100
+ template_file = "#{File.dirname(__FILE__)}/reporter/views/#{template_name}.html.erb"
101
+ template = File.read(template_file).gsub(/^ /, '')
102
+ ERB.new(template).result(binding)
103
+ end
104
+
105
+ def mass_transcode(combinations)
106
+ results = {}
107
+ combinations.each do |file, recipes|
108
+ results[file] = {}
109
+ recipes.each do |recipe_name, recipe|
110
+ puts "Transcoding #{File.basename(file)} using recipe #{recipe_name}"
111
+
112
+ #generate input/output file paths
113
+ input_file = File.expand_path(file)
114
+ output_file = generate_output_file_using input_file, recipe_name, recipe
115
+ #raise output_file
116
+ #input_file.gsub!(" ","\\ ")
117
+ input_file = "#{File.dirname(input_file)}/\"#{File.basename(input_file)}\""
118
+
119
+ #create logfile
120
+ log_file_name = underscoreize_file_basename(input_file) + "_" + recipe_name + ".log"
121
+ log_file = create_log_file(log_file_name)
122
+ RVideo.logger = Logger.new(log_file)
123
+
124
+ transcoder, errors = transcode(recipe, input_file, output_file)
125
+
126
+ #build the results object for the views
127
+ results[file][recipe_name] = {}
128
+ results[file][recipe_name]['output_file'] = output_file
129
+ results[file][recipe_name]['transcoder'] = transcoder
130
+ results[file][recipe_name]['errors'] = errors
131
+ results[file][recipe_name]['recipe'] = recipe
132
+ results[file][recipe_name]['log'] = log_file
133
+ end
134
+ end
135
+ return results
136
+ end
137
+
138
+ def generate_output_file_using(selected_file, recipe_name, recipe)
139
+ #File.join(@current_report_path, 'output_files')
140
+ output_path = File.join(@current_report_path, 'output_files' + underscoreize_file_basename(selected_file))
141
+ File.makedirs output_path
142
+ output_filename = "#{recipe_name}.#{recipe['extension']}"
143
+ output_file = File.join(output_path, output_filename)
144
+ #output_file.gsub(" ","_")
145
+ end
146
+
147
+ def underscoreize_file_basename(file)
148
+ File.basename(file).gsub(".","_").gsub(" ","_")
149
+ end
150
+
151
+ def transcode(recipe, input_file, output_file)
152
+ command = recipe['command']
153
+ errors = nil
154
+
155
+ #RVideo::Transcoder.logger = Logger.new(STDOUT)
156
+ begin
157
+ transcoder = RVideo::Transcoder.new
158
+ transcoder.execute(command, {:input_file => input_file,
159
+ :output_file => output_file})
160
+ #rescue => errors
161
+ end
162
+
163
+ return transcoder, errors
164
+ end
165
+
166
+ def create_log_file(log_file_name)
167
+ log_path = File.join(@current_report_path, "logs")
168
+ File.makedirs log_path
169
+ logfile = File.join(log_path, log_file_name)
170
+ File.open(logfile, "w+") { |file| }
171
+ logfile
172
+ end
173
+
174
+ end
175
+
176
+ end
@@ -0,0 +1,27 @@
1
+ <html>
2
+ <head>
3
+ <title>RVideo Reports Index</title>
4
+ <link rel="stylesheet" href="report.css" type="text/css" media="screen" />
5
+ <script type="text/javascript" src="report.js"></script>
6
+ </head>
7
+ <body>
8
+ <% @results.each do |input_file, recipes| %>
9
+ <h1>
10
+ <a href="<%= input_file %>"><%= File.basename(input_file) %> (launch file)</a>
11
+ </h1>
12
+ <ol>
13
+ <% recipes.each do |recipe_name, result| %>
14
+ <li>
15
+ <% css_class = 'warning' unless result['transcoder'].errors.empty? %>
16
+ <% css_class = 'critical' if result['errors'] %>
17
+ <% css_class = 'passed' if css_class.nil? %>
18
+ <div class="<%= css_class %>">
19
+ <% individual_report_url = "individual_reports/" + underscoreize_file_basename(input_file) + "_" + recipe_name + ".html" %>
20
+ <h2><a href="<%= result['output_file'] %>">Launch <%= recipe_name %></a> <a class="view-report" href="<%= individual_report_url %>" >view full report</a></h2>
21
+ </div>
22
+ </li>
23
+ <% end %>
24
+ </ol>
25
+ <% end %>
26
+ </body>
27
+ </html>
@@ -0,0 +1,27 @@
1
+ /* a { color: black; margin-left: 20px;}
2
+ a:visited { color: #111; }
3
+ .critical { background-color: #E6E6E6; border-left: 20px solid #F00; }
4
+ .warning { background-color: #E6E6E6; border-left: 20px solid orange; }
5
+ .passed { background-color: #E6E6E6; border-left: 20px solid #0F0; }
6
+
7
+ div { margin: 10px; border: 1px solid #ccc; padding: 5px; }
8
+ li { margin: 20px; padding: 5px; list-style-type: none; }
9
+ span { background-color: #ccc; padding: 5px; width: 500px;}
10
+ span:hover { cursor: pointer; text-decoration: underline; }
11
+ */
12
+
13
+ html { font-size: .75em;}
14
+ div { font-size: 1.2em;}
15
+ h1, h2, h3, h4, h5, ul { margin: .5em; }
16
+ a { color: black; margin-left: 2em; padding: 1em;}
17
+ a:visited { color: #111; }
18
+ .critical, .warning, .passed { background-color: #E6E6E6; border-left: 5em solid; }
19
+ .critical { border-left-color: #F00; }
20
+ .warning { border-left-color: orange; }
21
+ .passed { border-left-color: #0F0; }
22
+
23
+ div { margin: 1em; border: 1px solid #ccc; padding: .5em; }
24
+ ol li { margin: .01em; padding: 0; margin-left: 5em; }
25
+ ul li { margin: 1em; padding: 1em; list-style-type: none; }
26
+ span { background-color: #ccc; padding: 1em; width: 500px;}
27
+ span:hover { cursor: pointer; text-decoration: underline; }
@@ -0,0 +1,81 @@
1
+ <html>
2
+ <head>
3
+ <title>RVideo Reports</title>
4
+ <link rel="stylesheet" href="<%= report.css %>" type="text/css" media="screen" />
5
+ <script type="text/javascript" src="<%= report.js %>"></script>
6
+ </head>
7
+ <body>
8
+ <div>
9
+ <h2><a href="<%= @result['output_file'] %>">Launch output file</a></h2>
10
+ <ul>
11
+ <li>
12
+ <div>
13
+ <h2>Recipe: <%= @recipe_name %></h2>
14
+ <p>
15
+ <% @result['recipe'].each do |key, value|%>
16
+ <%= key %>: <%= value %><br />
17
+ <% end %>
18
+ </p>
19
+ </div>
20
+ </li>
21
+ <% unless @result['errors'].nil? %>
22
+ <li>
23
+ <span onclick="toggle('rescued-errors');">Hide/Show Rescued Errors</span>
24
+ <div id='rescued-errors' style="display: none;">
25
+ <h2>Rescued Error Backtrace</h2>
26
+ <h4><%= h(@result['errors'].class.name) %></h4>
27
+ <p><%= h(@result['errors'].message)%>
28
+ <p><%= h(@result['errors'].backtrace) %></p>
29
+ </div>
30
+ </li>
31
+ <% end %>
32
+ <li>
33
+ <span onclick="toggle('transcoder');">Hide/Show Transcoder</span>
34
+ <div id='transcoder' style="display: none;">
35
+ <h2>Transcoder</h2>
36
+ <% unless @result['transcoder'].errors.empty? %>
37
+ <h3>Transcoder Errors</h3>
38
+ <p><%= h(@result['transcoder'].errors.inspect) %></p>
39
+ <% end %>
40
+ <h3>Executed Commands</h3>
41
+ <p><%= h(@result['transcoder'].executed_commands.map(&:command)) %></p>
42
+ <h3>Raw Meta</h3>
43
+ <p><%= h(@result['transcoder'].metadata) %></p>
44
+ </div>
45
+ <li>
46
+ <span onclick="toggle('input-file');">Hide/Show Input File</span>
47
+ <div id='input-file' style="display: none;">
48
+ <h2>Original Input File</h2>
49
+ <h3>Raw Metadata</h3>
50
+ <p><%= h(@result['transcoder'].original.raw_metadata) %></p>
51
+ <h3>Raw Response</h3>
52
+ <p><%= h(@result['transcoder'].original.raw_response) %></p>
53
+ </div>
54
+ </li>
55
+ <% if @result['transcoder'].processed %>
56
+ <li>
57
+ <span onclick="toggle('output-file');">Hide/Show Output File</span>
58
+ <div id='output-file' style="display: none;">
59
+ <h2>Processed Output File</h2>
60
+ <h3>Raw Metadata</h3>
61
+ <p><%= h(@result['transcoder'].processed.raw_metadata) %></p>
62
+ <h3>Raw Response</h3>
63
+ <p><%= h(@result['transcoder'].processed.raw_response) %></p>
64
+ </div>
65
+ </li>
66
+ <% end %>
67
+ <li>
68
+ <span onclick="toggle('log');">Hide/Show Log</span>
69
+ <div id='log' style="display: none;">
70
+ <h2>Log</h2>
71
+ <p>
72
+ <% File.readlines(@result['log']).each do |line| %>
73
+ <%= line %> <br />
74
+ <% end %>
75
+ </p>
76
+ </div>
77
+ </li>
78
+ </ul>
79
+ </div>
80
+ </body>
81
+ </html>
@@ -0,0 +1,9 @@
1
+ function toggle(obj) {
2
+ var el = document.getElementById(obj);
3
+ if ( el.style.display != 'none' ) {
4
+ el.style.display = 'none';
5
+ }
6
+ else {
7
+ el.style.display = '';
8
+ }
9
+ }
@@ -0,0 +1,5 @@
1
+ class ::String
2
+ def shell_quoted
3
+ "'" << self << "'"
4
+ end
5
+ end
@@ -0,0 +1,414 @@
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.underscore.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, :deinterlace, :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
+
60
+ # nice the command if option :nice was given
61
+ # accepts a number 1..19 (nice value) or anything evaluating to true
62
+ unless RUBY_PLATFORM.downcase.include?("mswin")
63
+ if @options.has_key? 'nice'
64
+ if (1..19) === @options['nice']
65
+ final_command = "nice -n#{@options['nice']} #{final_command}"
66
+ elsif @options['nice']
67
+ final_command = "nice #{final_command}"
68
+ end
69
+ end
70
+ end
71
+
72
+ RVideo.logger.info("\nExecuting Command: #{final_command}\n")
73
+ do_execute final_command
74
+
75
+ populate_raw_result(log_temp_file_name)
76
+
77
+ RVideo.logger.info("Result: \n#{@raw_result}")
78
+ parse_result(@raw_result)
79
+
80
+ # Cleanup log file
81
+ begin
82
+ File.delete(log_temp_file_name)
83
+ rescue Exception => e
84
+ RVideo.logger.error("Failed to delete output log file: #{log_temp_file_name}, e=#{e}")
85
+ end
86
+ end
87
+
88
+ # Wrapper around the system call, for whenever we need to
89
+ # hook on or redefine this without messing with Kernel
90
+ def do_execute(command)
91
+ system command
92
+ end
93
+
94
+ #
95
+ # Magic parameters
96
+ #
97
+ def temp_dir
98
+ if @options['output_file']
99
+ "#{File.dirname(@options['output_file'])}/"
100
+ else
101
+ ""
102
+ end
103
+ end
104
+
105
+ ###
106
+ # FPS aka framerate
107
+
108
+ def fps
109
+ format_fps(get_fps)
110
+ end
111
+
112
+ def get_fps
113
+ inspect_original if @original.nil?
114
+ fps = @options['fps'] || ""
115
+ case fps
116
+ when "copy"
117
+ get_original_fps
118
+ else
119
+ get_specific_fps
120
+ end
121
+ end
122
+
123
+ def get_original_fps
124
+ return {} if @original.fps.nil?
125
+ { :fps => @original.fps }
126
+ end
127
+
128
+ def get_specific_fps
129
+ { :fps => @options['fps'] }
130
+ end
131
+
132
+ ###
133
+ # Resolution
134
+
135
+ def deinterlace
136
+ format_deinterlace(get_deinterlace)
137
+ end
138
+
139
+ def get_deinterlace
140
+ { :deinterlace => @options['deinterlace'] ? true : false }
141
+ end
142
+
143
+ def resolution
144
+ format_resolution(get_resolution)
145
+ end
146
+
147
+ def get_resolution
148
+ inspect_original if @original.nil?
149
+
150
+ case @options['resolution']
151
+ when "copy" then get_original_resolution
152
+ when "width" then get_fit_to_width_resolution
153
+ when "height" then get_fit_to_height_resolution
154
+ when "letterbox" then get_letterbox_resolution
155
+ else
156
+ if @options["width"] and not @options["height"]
157
+ get_fit_to_width_resolution
158
+ elsif @options["height"] and not @options["width"]
159
+ get_fit_to_height_resolution
160
+ elsif @options["width"] and @options["height"]
161
+ get_specific_resolution
162
+ else
163
+ get_original_resolution
164
+ end
165
+ end
166
+ end
167
+
168
+ def get_fit_to_width_resolution
169
+ w = @options['width']
170
+
171
+ raise TranscoderError::ParameterError,
172
+ "invalid width of '#{w}' for fit to width" unless valid_dimension?(w)
173
+
174
+ h = calculate_height(@original.width, @original.height, w)
175
+
176
+ { :scale => { :width => w, :height => h } }
177
+ end
178
+
179
+ def get_fit_to_height_resolution
180
+ h = @options['height']
181
+
182
+ raise TranscoderError::ParameterError,
183
+ "invalid height of '#{h}' for fit to height" unless valid_dimension?(h)
184
+
185
+ w = calculate_width(@original.width, @original.height, h)
186
+
187
+ { :scale => { :width => w, :height => h } }
188
+ end
189
+
190
+ def get_letterbox_resolution
191
+ lw = @options['width'].to_i
192
+ lh = @options['height'].to_i
193
+
194
+ raise TranscoderError::ParameterError,
195
+ "invalid width of '#{lw}' for letterbox" unless valid_dimension?(lw)
196
+ raise TranscoderError::ParameterError,
197
+ "invalid height of '#{lh}' for letterbox" unless valid_dimension?(lh)
198
+
199
+ w = calculate_width(@original.width, @original.height, lh)
200
+ h = calculate_height(@original.width, @original.height, lw)
201
+
202
+ if w > lw
203
+ w = lw
204
+ h = calculate_height(@original.width, @original.height, lw)
205
+ else
206
+ h = lh
207
+ w = calculate_width(@original.width, @original.height, lh)
208
+ end
209
+
210
+ { :scale => { :width => w, :height => h },
211
+ :letterbox => { :width => lw, :height => lh } }
212
+ end
213
+
214
+ def get_original_resolution
215
+ { :scale => { :width => @original.width, :height => @original.height } }
216
+ end
217
+
218
+ def get_specific_resolution
219
+ w = @options['width']
220
+ h = @options['height']
221
+
222
+ raise TranscoderError::ParameterError,
223
+ "invalid width of '#{w}' for specific resolution" unless valid_dimension?(w)
224
+ raise TranscoderError::ParameterError,
225
+ "invalid height of '#{h}' for specific resolution" unless valid_dimension?(h)
226
+
227
+ { :scale => { :width => w, :height => h } }
228
+ end
229
+
230
+ def calculate_width(ow, oh, h)
231
+ w = ((ow.to_f / oh.to_f) * h.to_f).to_i
232
+ (w.to_f / 16).round * 16
233
+ end
234
+
235
+ def calculate_height(ow, oh, w)
236
+ h = (w.to_f / (ow.to_f / oh.to_f)).to_i
237
+ (h.to_f / 16).round * 16
238
+ end
239
+
240
+ def valid_dimension?(dim)
241
+ dim.to_i > 0
242
+ end
243
+
244
+ ###
245
+ # Audio channels
246
+
247
+ def audio_channels
248
+ format_audio_channels(get_audio_channels)
249
+ end
250
+
251
+ def get_audio_channels
252
+ channels = @options['audio_channels'] || ""
253
+ case channels
254
+ when "stereo"
255
+ get_stereo_audio
256
+ when "mono"
257
+ get_mono_audio
258
+ else
259
+ {}
260
+ end
261
+ end
262
+
263
+ def get_stereo_audio
264
+ { :channels => "2" }
265
+ end
266
+
267
+ def get_mono_audio
268
+ { :channels => "1" }
269
+ end
270
+
271
+ def get_specific_audio_bit_rate
272
+ { :bit_rate => @options['audio_bit_rate'] }
273
+ end
274
+
275
+ def get_specific_audio_sample_rate
276
+ { :sample_rate => @options['audio_sample_rate'] }
277
+ end
278
+
279
+ ###
280
+ # Audio bit rate
281
+
282
+ def audio_bit_rate
283
+ format_audio_bit_rate(get_audio_bit_rate)
284
+ end
285
+
286
+ def get_audio_bit_rate
287
+ bit_rate = @options['audio_bit_rate'] || ""
288
+ case bit_rate
289
+ when ""
290
+ {}
291
+ else
292
+ get_specific_audio_bit_rate
293
+ end
294
+ end
295
+
296
+ ###
297
+ # Audio sample rate
298
+
299
+ def audio_sample_rate
300
+ format_audio_sample_rate(get_audio_sample_rate)
301
+ end
302
+
303
+ def get_audio_sample_rate
304
+ sample_rate = @options['audio_sample_rate'] || ""
305
+ case sample_rate
306
+ when ""
307
+ {}
308
+ else
309
+ get_specific_audio_sample_rate
310
+ end
311
+ end
312
+
313
+ ###
314
+ # Video quality
315
+
316
+ def video_quality
317
+ format_video_quality(get_video_quality)
318
+ end
319
+
320
+ def get_video_quality
321
+ quality = @options['video_quality'] || 'medium'
322
+
323
+ { :video_quality => quality }.
324
+ merge!(get_fps).
325
+ merge!(get_resolution).
326
+ merge!(get_video_bit_rate)
327
+ end
328
+
329
+ def video_bit_rate
330
+ format_video_bit_rate(get_video_bit_rate)
331
+ end
332
+
333
+ def get_video_bit_rate
334
+ { :video_bit_rate => @options["video_bit_rate"] }
335
+ end
336
+
337
+ def video_bit_rate_tolerance
338
+ format_video_bit_rate_tolerance(get_video_bit_rate_tolerance)
339
+ end
340
+
341
+ def get_video_bit_rate_tolerance
342
+ { :video_bit_rate_tolerance => @options["video_bit_rate_tolerance"] }
343
+ end
344
+
345
+ def video_bit_rate_min
346
+ format_video_bit_rate_min(get_video_bit_rate_min)
347
+ end
348
+
349
+ def get_video_bit_rate_min
350
+ { :video_bit_rate_min => @options["video_bit_rate_min"] }
351
+ end
352
+
353
+ def video_bit_rate_max
354
+ format_video_bit_rate_max(get_video_bit_rate_max)
355
+ end
356
+
357
+ def get_video_bit_rate_max
358
+ { :video_bit_rate_max => @options["video_bit_rate_max"] }
359
+ end
360
+
361
+ private
362
+
363
+ VARIABLE_INTERPOLATION_SCAN_PATTERN = /[^\\]\$[-_a-zA-Z]+\$/
364
+
365
+ def interpolate_variables(raw_command)
366
+ raw_command.scan(VARIABLE_INTERPOLATION_SCAN_PATTERN).each do |match|
367
+ match = match[0..0] == "$" ? match : match[1..(match.size - 1)]
368
+ match.strip!
369
+
370
+ value = if ["$input_file$", "$output_file$"].include?(match)
371
+ matched_variable(match).to_s.shell_quoted
372
+ else
373
+ matched_variable(match).to_s
374
+ end
375
+
376
+ raw_command.gsub!(match, value)
377
+ end
378
+ raw_command.gsub("\\$", "$")
379
+ end
380
+
381
+ #
382
+ # Strip the $s. First, look for a supplied option that matches the
383
+ # variable name. If one is not found, look for a method that matches.
384
+ # If not found, raise ParameterError exception.
385
+ #
386
+
387
+ def matched_variable(match)
388
+ variable_name = match.gsub("$","")
389
+ if self.respond_to? variable_name
390
+ self.send(variable_name)
391
+ elsif @options.key?(variable_name)
392
+ @options[variable_name]
393
+ else
394
+ raise TranscoderError::ParameterError,
395
+ "command is looking for the #{variable_name} parameter, but it was not provided. (Command: #{@raw_command})"
396
+ end
397
+ end
398
+
399
+
400
+ def inspect_original
401
+ @original = Inspector.new(:file => options[:input_file])
402
+ end
403
+
404
+ # Pulls the interesting bits of the temp log file into memory. This is fairly tool-specific, so
405
+ # it's doubtful that this default version is going to work without being overridded.
406
+ def populate_raw_result(temp_file_name)
407
+ @raw_result = `tail -n 500 #{temp_file_name}`
408
+ end
409
+
410
+ end # InstanceMethods
411
+ end
412
+
413
+ end
414
+ end