twinge-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 (68) 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/config/boot.rb +25 -0
  10. data/lib/rvideo.rb +44 -0
  11. data/lib/rvideo/errors.rb +24 -0
  12. data/lib/rvideo/float.rb +7 -0
  13. data/lib/rvideo/frame_capturer.rb +126 -0
  14. data/lib/rvideo/inspector.rb +481 -0
  15. data/lib/rvideo/reporter.rb +176 -0
  16. data/lib/rvideo/reporter/views/index.html.erb +27 -0
  17. data/lib/rvideo/reporter/views/report.css +27 -0
  18. data/lib/rvideo/reporter/views/report.html.erb +81 -0
  19. data/lib/rvideo/reporter/views/report.js +9 -0
  20. data/lib/rvideo/string.rb +5 -0
  21. data/lib/rvideo/tools/abstract_tool.rb +401 -0
  22. data/lib/rvideo/tools/ffmpeg.rb +277 -0
  23. data/lib/rvideo/tools/ffmpeg2theora.rb +42 -0
  24. data/lib/rvideo/tools/flvtool2.rb +50 -0
  25. data/lib/rvideo/tools/mencoder.rb +103 -0
  26. data/lib/rvideo/tools/mp4box.rb +21 -0
  27. data/lib/rvideo/tools/mp4creator.rb +35 -0
  28. data/lib/rvideo/tools/mplayer.rb +31 -0
  29. data/lib/rvideo/tools/qtfaststart.rb +37 -0
  30. data/lib/rvideo/tools/yamdi.rb +44 -0
  31. data/lib/rvideo/transcoder.rb +120 -0
  32. data/lib/rvideo/version.rb +9 -0
  33. data/rvideo.gemspec +37 -0
  34. data/scripts/txt2html +67 -0
  35. data/setup.rb +1585 -0
  36. data/spec/files/boat.avi +0 -0
  37. data/spec/files/kites.mp4 +0 -0
  38. data/spec/fixtures/ffmpeg_builds.yml +28 -0
  39. data/spec/fixtures/ffmpeg_results.yml +608 -0
  40. data/spec/fixtures/files.yml +398 -0
  41. data/spec/fixtures/recipes.yml +58 -0
  42. data/spec/integrations/formats_spec.rb +315 -0
  43. data/spec/integrations/frame_capturer_spec.rb +26 -0
  44. data/spec/integrations/inspection_spec.rb +112 -0
  45. data/spec/integrations/recipes_spec.rb +0 -0
  46. data/spec/integrations/rvideo_spec.rb +17 -0
  47. data/spec/integrations/transcoder_integration_spec.rb +29 -0
  48. data/spec/integrations/transcoding_spec.rb +9 -0
  49. data/spec/spec.opts +1 -0
  50. data/spec/spec_helper.rb +16 -0
  51. data/spec/support.rb +36 -0
  52. data/spec/units/abstract_tool_spec.rb +111 -0
  53. data/spec/units/ffmpeg_spec.rb +323 -0
  54. data/spec/units/flvtool2_spec.rb +324 -0
  55. data/spec/units/frame_capturer_spec.rb +72 -0
  56. data/spec/units/inspector_spec.rb +59 -0
  57. data/spec/units/mencoder_spec.rb +4994 -0
  58. data/spec/units/mp4box_spec.rb +34 -0
  59. data/spec/units/mp4creator_spec.rb +34 -0
  60. data/spec/units/mplayer_spec.rb +34 -0
  61. data/spec/units/qtfaststart_spec.rb +35 -0
  62. data/spec/units/string_spec.rb +8 -0
  63. data/spec/units/transcoder_spec.rb +156 -0
  64. data/tasks/deployment.rake +5 -0
  65. data/tasks/testing.rake +27 -0
  66. data/tasks/transcoding.rake +40 -0
  67. data/tasks/website.rake +8 -0
  68. metadata +175 -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,401 @@
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
+ RVideo.logger.info("\nExecuting Command: #{final_command}\n")
60
+ do_execute final_command
61
+
62
+ populate_raw_result(log_temp_file_name)
63
+
64
+ RVideo.logger.info("Result: \n#{@raw_result}")
65
+ parse_result(@raw_result)
66
+
67
+ # Cleanup log file
68
+ begin
69
+ File.delete(log_temp_file_name)
70
+ rescue Exception => e
71
+ RVideo.logger.error("Failed to delete output log file: #{log_temp_file_name}, e=#{e}")
72
+ end
73
+ end
74
+
75
+ # Wrapper around the system call, for whenever we need to
76
+ # hook on or redefine this without messing with Kernel
77
+ def do_execute(command)
78
+ system command
79
+ end
80
+
81
+ #
82
+ # Magic parameters
83
+ #
84
+ def temp_dir
85
+ if @options['output_file']
86
+ "#{File.dirname(@options['output_file'])}/"
87
+ else
88
+ ""
89
+ end
90
+ end
91
+
92
+ ###
93
+ # FPS aka framerate
94
+
95
+ def fps
96
+ format_fps(get_fps)
97
+ end
98
+
99
+ def get_fps
100
+ inspect_original if @original.nil?
101
+ fps = @options['fps'] || ""
102
+ case fps
103
+ when "copy"
104
+ get_original_fps
105
+ else
106
+ get_specific_fps
107
+ end
108
+ end
109
+
110
+ def get_original_fps
111
+ return {} if @original.fps.nil?
112
+ { :fps => @original.fps }
113
+ end
114
+
115
+ def get_specific_fps
116
+ { :fps => @options['fps'] }
117
+ end
118
+
119
+ ###
120
+ # Resolution
121
+
122
+ def deinterlace
123
+ format_deinterlace(get_deinterlace)
124
+ end
125
+
126
+ def get_deinterlace
127
+ { :deinterlace => @options['deinterlace'] ? true : false }
128
+ end
129
+
130
+ def resolution
131
+ format_resolution(get_resolution)
132
+ end
133
+
134
+ def get_resolution
135
+ inspect_original if @original.nil?
136
+
137
+ case @options['resolution']
138
+ when "copy" then get_original_resolution
139
+ when "width" then get_fit_to_width_resolution
140
+ when "height" then get_fit_to_height_resolution
141
+ when "letterbox" then get_letterbox_resolution
142
+ else
143
+ if @options["width"] and not @options["height"]
144
+ get_fit_to_width_resolution
145
+ elsif @options["height"] and not @options["width"]
146
+ get_fit_to_height_resolution
147
+ elsif @options["width"] and @options["height"]
148
+ get_specific_resolution
149
+ else
150
+ get_original_resolution
151
+ end
152
+ end
153
+ end
154
+
155
+ def get_fit_to_width_resolution
156
+ w = @options['width']
157
+
158
+ raise TranscoderError::ParameterError,
159
+ "invalid width of '#{w}' for fit to width" unless valid_dimension?(w)
160
+
161
+ h = calculate_height(@original.width, @original.height, w)
162
+
163
+ { :scale => { :width => w, :height => h } }
164
+ end
165
+
166
+ def get_fit_to_height_resolution
167
+ h = @options['height']
168
+
169
+ raise TranscoderError::ParameterError,
170
+ "invalid height of '#{h}' for fit to height" unless valid_dimension?(h)
171
+
172
+ w = calculate_width(@original.width, @original.height, h)
173
+
174
+ { :scale => { :width => w, :height => h } }
175
+ end
176
+
177
+ def get_letterbox_resolution
178
+ lw = @options['width'].to_i
179
+ lh = @options['height'].to_i
180
+
181
+ raise TranscoderError::ParameterError,
182
+ "invalid width of '#{lw}' for letterbox" unless valid_dimension?(lw)
183
+ raise TranscoderError::ParameterError,
184
+ "invalid height of '#{lh}' for letterbox" unless valid_dimension?(lh)
185
+
186
+ w = calculate_width(@original.width, @original.height, lh)
187
+ h = calculate_height(@original.width, @original.height, lw)
188
+
189
+ if w > lw
190
+ w = lw
191
+ h = calculate_height(@original.width, @original.height, lw)
192
+ else
193
+ h = lh
194
+ w = calculate_width(@original.width, @original.height, lh)
195
+ end
196
+
197
+ { :scale => { :width => w, :height => h },
198
+ :letterbox => { :width => lw, :height => lh } }
199
+ end
200
+
201
+ def get_original_resolution
202
+ { :scale => { :width => @original.width, :height => @original.height } }
203
+ end
204
+
205
+ def get_specific_resolution
206
+ w = @options['width']
207
+ h = @options['height']
208
+
209
+ raise TranscoderError::ParameterError,
210
+ "invalid width of '#{w}' for specific resolution" unless valid_dimension?(w)
211
+ raise TranscoderError::ParameterError,
212
+ "invalid height of '#{h}' for specific resolution" unless valid_dimension?(h)
213
+
214
+ { :scale => { :width => w, :height => h } }
215
+ end
216
+
217
+ def calculate_width(ow, oh, h)
218
+ w = ((ow.to_f / oh.to_f) * h.to_f).to_i
219
+ (w.to_f / 16).round * 16
220
+ end
221
+
222
+ def calculate_height(ow, oh, w)
223
+ h = (w.to_f / (ow.to_f / oh.to_f)).to_i
224
+ (h.to_f / 16).round * 16
225
+ end
226
+
227
+ def valid_dimension?(dim)
228
+ dim.to_i > 0
229
+ end
230
+
231
+ ###
232
+ # Audio channels
233
+
234
+ def audio_channels
235
+ format_audio_channels(get_audio_channels)
236
+ end
237
+
238
+ def get_audio_channels
239
+ channels = @options['audio_channels'] || ""
240
+ case channels
241
+ when "stereo"
242
+ get_stereo_audio
243
+ when "mono"
244
+ get_mono_audio
245
+ else
246
+ {}
247
+ end
248
+ end
249
+
250
+ def get_stereo_audio
251
+ { :channels => "2" }
252
+ end
253
+
254
+ def get_mono_audio
255
+ { :channels => "1" }
256
+ end
257
+
258
+ def get_specific_audio_bit_rate
259
+ { :bit_rate => @options['audio_bit_rate'] }
260
+ end
261
+
262
+ def get_specific_audio_sample_rate
263
+ { :sample_rate => @options['audio_sample_rate'] }
264
+ end
265
+
266
+ ###
267
+ # Audio bit rate
268
+
269
+ def audio_bit_rate
270
+ format_audio_bit_rate(get_audio_bit_rate)
271
+ end
272
+
273
+ def get_audio_bit_rate
274
+ bit_rate = @options['audio_bit_rate'] || ""
275
+ case bit_rate
276
+ when ""
277
+ {}
278
+ else
279
+ get_specific_audio_bit_rate
280
+ end
281
+ end
282
+
283
+ ###
284
+ # Audio sample rate
285
+
286
+ def audio_sample_rate
287
+ format_audio_sample_rate(get_audio_sample_rate)
288
+ end
289
+
290
+ def get_audio_sample_rate
291
+ sample_rate = @options['audio_sample_rate'] || ""
292
+ case sample_rate
293
+ when ""
294
+ {}
295
+ else
296
+ get_specific_audio_sample_rate
297
+ end
298
+ end
299
+
300
+ ###
301
+ # Video quality
302
+
303
+ def video_quality
304
+ format_video_quality(get_video_quality)
305
+ end
306
+
307
+ def get_video_quality
308
+ quality = @options['video_quality'] || 'medium'
309
+
310
+ { :video_quality => quality }.
311
+ merge!(get_fps).
312
+ merge!(get_resolution).
313
+ merge!(get_video_bit_rate)
314
+ end
315
+
316
+ def video_bit_rate
317
+ format_video_bit_rate(get_video_bit_rate)
318
+ end
319
+
320
+ def get_video_bit_rate
321
+ { :video_bit_rate => @options["video_bit_rate"] }
322
+ end
323
+
324
+ def video_bit_rate_tolerance
325
+ format_video_bit_rate_tolerance(get_video_bit_rate_tolerance)
326
+ end
327
+
328
+ def get_video_bit_rate_tolerance
329
+ { :video_bit_rate_tolerance => @options["video_bit_rate_tolerance"] }
330
+ end
331
+
332
+ def video_bit_rate_min
333
+ format_video_bit_rate_min(get_video_bit_rate_min)
334
+ end
335
+
336
+ def get_video_bit_rate_min
337
+ { :video_bit_rate_min => @options["video_bit_rate_min"] }
338
+ end
339
+
340
+ def video_bit_rate_max
341
+ format_video_bit_rate_max(get_video_bit_rate_max)
342
+ end
343
+
344
+ def get_video_bit_rate_max
345
+ { :video_bit_rate_max => @options["video_bit_rate_max"] }
346
+ end
347
+
348
+ private
349
+
350
+ VARIABLE_INTERPOLATION_SCAN_PATTERN = /[^\\]\$[-_a-zA-Z]+\$/
351
+
352
+ def interpolate_variables(raw_command)
353
+ raw_command.scan(VARIABLE_INTERPOLATION_SCAN_PATTERN).each do |match|
354
+ match = match[0..0] == "$" ? match : match[1..(match.size - 1)]
355
+ match.strip!
356
+
357
+ value = if ["$input_file$", "$output_file$"].include?(match)
358
+ matched_variable(match).to_s.shell_quoted
359
+ else
360
+ matched_variable(match).to_s
361
+ end
362
+
363
+ raw_command.gsub!(match, value)
364
+ end
365
+ raw_command.gsub("\\$", "$")
366
+ end
367
+
368
+ #
369
+ # Strip the $s. First, look for a supplied option that matches the
370
+ # variable name. If one is not found, look for a method that matches.
371
+ # If not found, raise ParameterError exception.
372
+ #
373
+
374
+ def matched_variable(match)
375
+ variable_name = match.gsub("$","")
376
+ if self.respond_to? variable_name
377
+ self.send(variable_name)
378
+ elsif @options.key?(variable_name)
379
+ @options[variable_name]
380
+ else
381
+ raise TranscoderError::ParameterError,
382
+ "command is looking for the #{variable_name} parameter, but it was not provided. (Command: #{@raw_command})"
383
+ end
384
+ end
385
+
386
+
387
+ def inspect_original
388
+ @original = Inspector.new(:file => options[:input_file])
389
+ end
390
+
391
+ # Pulls the interesting bits of the temp log file into memory. This is fairly tool-specific, so
392
+ # it's doubtful that this default version is going to work without being overridded.
393
+ def populate_raw_result(temp_file_name)
394
+ @raw_result = `tail -n 500 #{temp_file_name}`
395
+ end
396
+
397
+ end # InstanceMethods
398
+ end
399
+
400
+ end
401
+ end