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.
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