stlondemand-rvideo 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) 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 +72 -0
  6. data/README +106 -0
  7. data/RULES +11 -0
  8. data/Rakefile +63 -0
  9. data/config/boot.rb +25 -0
  10. data/lib/rvideo.rb +49 -0
  11. data/lib/rvideo/command_executor.rb +91 -0
  12. data/lib/rvideo/errors.rb +24 -0
  13. data/lib/rvideo/float.rb +7 -0
  14. data/lib/rvideo/frame_capturer.rb +139 -0
  15. data/lib/rvideo/inspector.rb +519 -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 +459 -0
  23. data/lib/rvideo/tools/ffmpeg.rb +314 -0
  24. data/lib/rvideo/tools/ffmpeg2theora.rb +72 -0
  25. data/lib/rvideo/tools/flvtool2.rb +50 -0
  26. data/lib/rvideo/tools/handbrakecli.rb +61 -0
  27. data/lib/rvideo/tools/lame.rb +58 -0
  28. data/lib/rvideo/tools/mencoder.rb +126 -0
  29. data/lib/rvideo/tools/mp4box.rb +21 -0
  30. data/lib/rvideo/tools/mp4creator.rb +35 -0
  31. data/lib/rvideo/tools/mplayer.rb +31 -0
  32. data/lib/rvideo/tools/qtfaststart.rb +37 -0
  33. data/lib/rvideo/tools/segmenter.rb +29 -0
  34. data/lib/rvideo/tools/yamdi.rb +44 -0
  35. data/lib/rvideo/transcoder.rb +170 -0
  36. data/lib/rvideo/version.rb +9 -0
  37. data/scripts/txt2html +67 -0
  38. data/spec/files/boat.avi +0 -0
  39. data/spec/files/kites.mp4 +0 -0
  40. data/spec/fixtures/ffmpeg_builds.yml +28 -0
  41. data/spec/fixtures/ffmpeg_results.yml +608 -0
  42. data/spec/fixtures/files.yml +398 -0
  43. data/spec/fixtures/recipes.yml +58 -0
  44. data/spec/integrations/formats_spec.rb +315 -0
  45. data/spec/integrations/frame_capturer_spec.rb +26 -0
  46. data/spec/integrations/inspection_spec.rb +125 -0
  47. data/spec/integrations/recipes_spec.rb +0 -0
  48. data/spec/integrations/rvideo_spec.rb +17 -0
  49. data/spec/integrations/transcoder_integration_spec.rb +29 -0
  50. data/spec/integrations/transcoding_spec.rb +9 -0
  51. data/spec/spec.opts +1 -0
  52. data/spec/spec_helper.rb +16 -0
  53. data/spec/support.rb +36 -0
  54. data/spec/units/abstract_tool_spec.rb +111 -0
  55. data/spec/units/command_executor_spec.rb +106 -0
  56. data/spec/units/ffmpeg_spec.rb +385 -0
  57. data/spec/units/flvtool2_spec.rb +323 -0
  58. data/spec/units/frame_capturer_spec.rb +71 -0
  59. data/spec/units/inspector_spec.rb +59 -0
  60. data/spec/units/mencoder_spec.rb +4994 -0
  61. data/spec/units/mp4box_spec.rb +34 -0
  62. data/spec/units/mp4creator_spec.rb +34 -0
  63. data/spec/units/mplayer_spec.rb +34 -0
  64. data/spec/units/qtfaststart_spec.rb +35 -0
  65. data/spec/units/string_spec.rb +8 -0
  66. data/spec/units/transcoder_spec.rb +154 -0
  67. data/stlondemand-rvideo.gemspec +36 -0
  68. data/tasks/deployment.rake +5 -0
  69. data/tasks/testing.rake +27 -0
  70. data/tasks/transcoding.rake +40 -0
  71. data/tasks/website.rake +8 -0
  72. data/test_progress_reporting.rb +14 -0
  73. metadata +187 -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,459 @@
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_class_name = tool_name.underscore.classify
12
+ tool = RVideo::Tools.const_get(tool_class_name).new(cmd, options)
13
+ rescue NameError => e
14
+ raise TranscoderError::UnknownTool,
15
+ "The recipe tried to use #{tool_name.inspect}, " +
16
+ "which expands to #{tool_class_name.inspect}, which does not exist. \n#{e.message}"
17
+ rescue Object => e
18
+ RVideo.logger.info e.message
19
+ raise e
20
+ end
21
+ end
22
+
23
+
24
+ module InstanceMethods
25
+ # Defines abstract methods in the convention of "format_#{attribute}"
26
+ # which are meant to be redefined by classes including this behavior.
27
+ def self.abstract_attribute_formatter(*names)
28
+ names.map { |n| "format_#{n}" }.each do |name|
29
+ class_eval %{
30
+ def #{name}(params = {})
31
+ raise ParameterError,
32
+ "The #{self.class} tool has not implemented the :#{name} method."
33
+ end
34
+ }, __FILE__, __LINE__
35
+ end
36
+ end
37
+
38
+ abstract_attribute_formatter :resolution, :deinterlace, :fps,
39
+ :video_bit_rate, :video_bit_rate_tolerance,
40
+ :video_bit_rate_min, :video_bit_rate_max,
41
+ :audio_channels, :audio_bit_rate, :audio_sample_rate
42
+
43
+ ###
44
+
45
+ attr_reader :options, :command, :raw_result, :progress
46
+
47
+ def initialize(raw_command, options = {})
48
+ @raw_command = raw_command
49
+ @options = HashWithIndifferentAccess.new(options)
50
+ @command = interpolate_variables(raw_command)
51
+ @raw_result = ""
52
+ end
53
+
54
+ def execute
55
+ @output_params = {}
56
+ if block_given? and self.respond_to?(:execute_with_progress)
57
+ execute_with_progress do |progress|
58
+ yield progress if progress != @progress
59
+ @progress = progress
60
+ end
61
+
62
+ if @progress != 100
63
+ @progress = 100
64
+ yield @progress
65
+ end
66
+ else
67
+ RVideo.logger.info("\nExecuting Command: #{@command}\n")
68
+ @raw_result = do_execute(@command)
69
+ end
70
+
71
+ RVideo.logger.info("Result: \n#{@raw_result}")
72
+ parse_result(@raw_result)
73
+ end
74
+
75
+ def do_execute(command)
76
+ CommandExecutor::execute_tailing_stderr(command, 500)
77
+ end
78
+
79
+ #
80
+ # Magic parameters
81
+ #
82
+ def temp_dir
83
+ if @options['output_file']
84
+ "#{File.dirname(@options['output_file'])}/"
85
+ else
86
+ ""
87
+ end
88
+ end
89
+
90
+ ###
91
+ # FPS aka framerate
92
+
93
+ def fps
94
+ format_fps(get_fps)
95
+ end
96
+
97
+ def get_fps
98
+ fps = @options['fps'] || ""
99
+ case fps
100
+ when "copy"
101
+ get_original_fps
102
+ else
103
+ get_specific_fps
104
+ end
105
+ end
106
+
107
+ def get_original_fps
108
+ return {} if original.fps.nil?
109
+ { :fps => original.fps }
110
+ end
111
+
112
+ def get_specific_fps
113
+ { :fps => @options['fps'] }
114
+ end
115
+
116
+ ###
117
+ # Resolution
118
+
119
+ def deinterlace
120
+ format_deinterlace(get_deinterlace)
121
+ end
122
+
123
+ def get_deinterlace
124
+ { :deinterlace => @options['deinterlace'] ? true : false }
125
+ end
126
+
127
+ def resolution
128
+ format_resolution(get_resolution)
129
+ end
130
+
131
+ def resolution_and_padding
132
+ format_resolution(get_padding_resolution)
133
+ end
134
+
135
+ def resolution_keep_aspect_ratio
136
+ format_resolution(get_constrain_resolution)
137
+ end
138
+
139
+ def get_resolution
140
+
141
+ case @options['resolution']
142
+ when "copy" then get_original_resolution
143
+ when "width" then get_fit_to_width_resolution
144
+ when "height" then get_fit_to_height_resolution
145
+ when "letterbox" then get_letterbox_resolution
146
+ else
147
+ if @options["width"] and not @options["height"]
148
+ get_fit_to_width_resolution
149
+ elsif @options["height"] and not @options["width"]
150
+ get_fit_to_height_resolution
151
+ elsif @options["width"] and @options["height"]
152
+ get_specific_resolution
153
+ else
154
+ get_original_resolution
155
+ end
156
+ end
157
+ end
158
+
159
+ def get_padding_resolution
160
+ lw = get_valid_width
161
+ lh = get_valid_height
162
+
163
+ raise TranscoderError::ParameterError,
164
+ "invalid width of '#{lw}' for letterbox" unless valid_dimension?(lw)
165
+ raise TranscoderError::ParameterError,
166
+ "invalid height of '#{lh}' for letterbox" unless valid_dimension?(lh)
167
+
168
+ w = lw
169
+ h = calculate_height(original.width, original.height, w)
170
+
171
+ if h > lh
172
+ h = lh
173
+ w = calculate_width(original.width, original.height, h)
174
+ end
175
+
176
+ { :scale => { :width => w, :height => h },
177
+ :letterbox => { :width => w, :height => lh } }
178
+ end
179
+
180
+ def get_constrain_resolution
181
+ lw = get_valid_width
182
+ lh = get_valid_height
183
+
184
+ raise TranscoderError::ParameterError,
185
+ "invalid width of '#{lw}' for letterbox" unless valid_dimension?(lw)
186
+ raise TranscoderError::ParameterError,
187
+ "invalid height of '#{lh}' for letterbox" unless valid_dimension?(lh)
188
+
189
+ w = lw
190
+ h = calculate_height(original.width, original.height, w)
191
+
192
+ if h > lh
193
+ h = lh
194
+ w = calculate_width(original.width, original.height, h)
195
+ end
196
+
197
+ { :scale => { :width => w, :height => h } }
198
+ end
199
+
200
+ def get_fit_to_width_resolution
201
+ w = get_valid_width
202
+
203
+ raise TranscoderError::ParameterError,
204
+ "invalid width of '#{w}' for fit to width" unless valid_dimension?(w)
205
+
206
+ h = calculate_height(original.width, original.height, w)
207
+
208
+ { :scale => { :width => w, :height => h } }
209
+ end
210
+
211
+ def get_fit_to_height_resolution
212
+ h = get_valid_height
213
+
214
+ raise TranscoderError::ParameterError,
215
+ "invalid height of '#{h}' for fit to height" unless valid_dimension?(h)
216
+
217
+ w = calculate_width(original.width, original.height, h)
218
+
219
+ { :scale => { :width => w, :height => h } }
220
+ end
221
+
222
+ def get_letterbox_resolution
223
+ lw = get_valid_width
224
+ lh = get_valid_height
225
+
226
+ raise TranscoderError::ParameterError,
227
+ "invalid width of '#{lw}' for letterbox" unless valid_dimension?(lw)
228
+ raise TranscoderError::ParameterError,
229
+ "invalid height of '#{lh}' for letterbox" unless valid_dimension?(lh)
230
+
231
+ w = lw
232
+ h = calculate_height(original.width, original.height, w)
233
+
234
+ if h > lh
235
+ h = lh
236
+ w = calculate_width(original.width, original.height, h)
237
+ end
238
+
239
+ { :scale => { :width => w, :height => h },
240
+ :letterbox => { :width => lw, :height => lh } }
241
+ end
242
+
243
+ def get_original_resolution
244
+ { :scale => { :width => original.width, :height => original.height } }
245
+ end
246
+
247
+ def get_specific_resolution
248
+ w = get_valid_width
249
+ h = get_valid_height
250
+
251
+ raise TranscoderError::ParameterError,
252
+ "invalid width of '#{w}' for specific resolution" unless valid_dimension?(w)
253
+ raise TranscoderError::ParameterError,
254
+ "invalid height of '#{h}' for specific resolution" unless valid_dimension?(h)
255
+
256
+ { :scale => { :width => w, :height => h } }
257
+ end
258
+
259
+ def calculate_width(ow, oh, h)
260
+ w = ((ow.to_f / oh.to_f) * h.to_f).to_f rescue get_valid_width
261
+ w = get_valid_width if w.nan?
262
+ w = w.ceil
263
+ (w - w % 2)
264
+ end
265
+
266
+ def calculate_height(ow, oh, w)
267
+ h = (w.to_f / (ow.to_f / oh.to_f)).to_f rescue get_valid_height
268
+ h = get_valid_height if h.nan?
269
+ h = h.ceil
270
+ (h - h % 2)
271
+ end
272
+
273
+ def valid_dimension?(dim)
274
+ dim.to_i > 0
275
+ end
276
+
277
+ def get_valid_width
278
+ @options[:width].to_i - (@options[:width].to_i % 2)
279
+ end
280
+
281
+ def get_valid_height
282
+ @options[:height].to_i - (@options[:height].to_i % 2)
283
+ end
284
+
285
+ ###
286
+ # Audio channels
287
+
288
+ def audio_channels
289
+ format_audio_channels(get_audio_channels)
290
+ end
291
+
292
+ def get_audio_channels
293
+ channels = @options['audio_channels'] || ""
294
+ case channels
295
+ when "stereo"
296
+ get_stereo_audio
297
+ when "mono"
298
+ get_mono_audio
299
+ else
300
+ {}
301
+ end
302
+ end
303
+
304
+ def get_stereo_audio
305
+ { :channels => "2" }
306
+ end
307
+
308
+ def get_mono_audio
309
+ { :channels => "1" }
310
+ end
311
+
312
+ def get_specific_audio_bit_rate
313
+ { :bit_rate => @options['audio_bit_rate'] }
314
+ end
315
+
316
+ def get_specific_audio_sample_rate
317
+ { :sample_rate => @options['audio_sample_rate'] }
318
+ end
319
+
320
+ ###
321
+ # Audio bit rate
322
+
323
+ def audio_bit_rate
324
+ format_audio_bit_rate(get_audio_bit_rate)
325
+ end
326
+
327
+ def get_audio_bit_rate
328
+ bit_rate = @options['audio_bit_rate'] || ""
329
+ case bit_rate
330
+ when ""
331
+ {}
332
+ else
333
+ get_specific_audio_bit_rate
334
+ end
335
+ end
336
+
337
+ ###
338
+ # Audio sample rate
339
+
340
+ def audio_sample_rate
341
+ format_audio_sample_rate(get_audio_sample_rate)
342
+ end
343
+
344
+ def get_audio_sample_rate
345
+ sample_rate = @options['audio_sample_rate'] || ""
346
+ case sample_rate
347
+ when ""
348
+ {}
349
+ else
350
+ get_specific_audio_sample_rate
351
+ end
352
+ end
353
+
354
+ ###
355
+ # Video quality
356
+
357
+ def video_quality
358
+ format_video_quality(get_video_quality)
359
+ end
360
+
361
+ def get_video_quality
362
+ quality = @options['video_quality'] || 'medium'
363
+
364
+ { :video_quality => quality }.
365
+ merge!(get_fps).
366
+ merge!(get_resolution).
367
+ merge!(get_video_bit_rate)
368
+ end
369
+
370
+ def video_bit_rate
371
+ format_video_bit_rate(get_video_bit_rate)
372
+ end
373
+
374
+ def get_video_bit_rate
375
+ { :video_bit_rate => @options["video_bit_rate"] }
376
+ end
377
+
378
+ def video_bit_rate_tolerance
379
+ format_video_bit_rate_tolerance(get_video_bit_rate_tolerance)
380
+ end
381
+
382
+ def get_video_bit_rate_tolerance
383
+ { :video_bit_rate_tolerance => @options["video_bit_rate_tolerance"] }
384
+ end
385
+
386
+ def video_bit_rate_min
387
+ format_video_bit_rate_min(get_video_bit_rate_min)
388
+ end
389
+
390
+ def get_video_bit_rate_min
391
+ { :video_bit_rate_min => @options["video_bit_rate_min"] }
392
+ end
393
+
394
+ def video_bit_rate_max
395
+ format_video_bit_rate_max(get_video_bit_rate_max)
396
+ end
397
+
398
+ def get_video_bit_rate_max
399
+ { :video_bit_rate_max => @options["video_bit_rate_max"] }
400
+ end
401
+
402
+ def original
403
+ @original || inspect_original
404
+ end
405
+
406
+ def original=(value)
407
+ @original = value
408
+ end
409
+
410
+
411
+ private
412
+
413
+ VARIABLE_INTERPOLATION_SCAN_PATTERN = /[^\\]\$[-_a-zA-Z]+\$/
414
+
415
+ def interpolate_variables(raw_command)
416
+ scan_command = raw_command.clone
417
+ scan_command.scan(VARIABLE_INTERPOLATION_SCAN_PATTERN).each do |match|
418
+ match = match[0..0] == "$" ? match : match[1..(match.size - 1)]
419
+ match.strip!
420
+
421
+ value = if ["$input_file$", "$output_file$"].include?(match)
422
+ matched_variable(match).to_s.shell_quoted
423
+ else
424
+ matched_variable(match).to_s
425
+ end
426
+
427
+ scan_command.gsub!(match, value)
428
+ end
429
+
430
+ scan_command.gsub("\\$", "$")
431
+ end
432
+
433
+ #
434
+ # Strip the $s. First, look for a supplied option that matches the
435
+ # variable name. If one is not found, look for a method that matches.
436
+ # If not found, raise ParameterError exception.
437
+ #
438
+
439
+ def matched_variable(match)
440
+ variable_name = match.gsub("$","")
441
+ if self.respond_to? variable_name
442
+ self.send(variable_name)
443
+ elsif @options.key?(variable_name)
444
+ @options[variable_name]
445
+ else
446
+ raise TranscoderError::ParameterError,
447
+ "command is looking for the #{variable_name} parameter, but it was not provided. (Command: #{@raw_command})"
448
+ end
449
+ end
450
+
451
+ def inspect_original
452
+ @original = Inspector.new(:file => options[:input_file])
453
+ end
454
+
455
+ end # InstanceMethods
456
+ end
457
+
458
+ end
459
+ end