vidibus-encoder 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.md +19 -0
- data/Rakefile +24 -0
- data/lib/vidibus/encoder/base.rb +222 -0
- data/lib/vidibus/encoder/helper/base.rb +47 -0
- data/lib/vidibus/encoder/helper/flags.rb +35 -0
- data/lib/vidibus/encoder/helper/tools.rb +30 -0
- data/lib/vidibus/encoder/helper.rb +3 -0
- data/lib/vidibus/encoder/util/flags.rb +88 -0
- data/lib/vidibus/encoder/util/input.rb +63 -0
- data/lib/vidibus/encoder/util/output.rb +98 -0
- data/lib/vidibus/encoder/util/profile.rb +173 -0
- data/lib/vidibus/encoder/util/profiles.rb +119 -0
- data/lib/vidibus/encoder/util/tmp.rb +44 -0
- data/lib/vidibus/encoder/util.rb +6 -0
- data/lib/vidibus/encoder/version.rb +5 -0
- data/lib/vidibus/encoder.rb +45 -0
- data/lib/vidibus-encoder.rb +5 -0
- metadata +215 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 André Pankratz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Vidibus::Encoder
|
2
|
+
|
3
|
+
This is a framework for creating custom encoders.
|
4
|
+
|
5
|
+
This gem is part of [Vidibus](http://vidibus.org), an open source toolset for building distributed (video) applications.
|
6
|
+
|
7
|
+
**Beware:** Work in progress!
|
8
|
+
|
9
|
+
|
10
|
+
## Notes
|
11
|
+
|
12
|
+
Use FFMpeg's cropdetect to cut off black bars:
|
13
|
+
```
|
14
|
+
ffmpeg -ss 600 -t 100 -i [input video] -vf "select='isnan(prev_selected_t)+gte(t-prev_selected_t,1)',cropdetect=24:2:0" -an -y null.mp4
|
15
|
+
```
|
16
|
+
|
17
|
+
## Copyright
|
18
|
+
|
19
|
+
© 2012 André Pankratz. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.unshift File.expand_path('../lib/', __FILE__)
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
require 'rdoc/task'
|
5
|
+
require 'rspec'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
|
8
|
+
Bundler::GemHelper.install_tasks
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
11
|
+
t.pattern = 'spec/**/*_spec.rb'
|
12
|
+
t.rcov = true
|
13
|
+
t.rcov_opts = ['--exclude', '^spec,/gems/']
|
14
|
+
end
|
15
|
+
|
16
|
+
Rake::RDocTask.new do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'Vidibus::Encoder'
|
19
|
+
rdoc.rdoc_files.include('README*')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
rdoc.options << '--charset=utf-8'
|
22
|
+
end
|
23
|
+
|
24
|
+
task :default => :rcov
|
@@ -0,0 +1,222 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
|
4
|
+
# This is the main encoder that you can build your own encoders on.
|
5
|
+
#
|
6
|
+
# The workflow of a encoder is as follows:
|
7
|
+
#
|
8
|
+
# initialize
|
9
|
+
# run
|
10
|
+
# validate_options
|
11
|
+
# prepare
|
12
|
+
# => for each profile:
|
13
|
+
# preprocess
|
14
|
+
# next unless process?
|
15
|
+
# process
|
16
|
+
# postprocess
|
17
|
+
# finish
|
18
|
+
#
|
19
|
+
# Each step of the workflow is represented by a method that you may
|
20
|
+
# redefine in your custom encoder class.
|
21
|
+
class Base
|
22
|
+
extend Helper::Flags
|
23
|
+
include Helper::Base
|
24
|
+
include Helper::Tools
|
25
|
+
|
26
|
+
attr_reader :options, :tmp, :input, :output, :profile, :profiles
|
27
|
+
|
28
|
+
# Initialize a encoder instance with given options. Two options are
|
29
|
+
# mandatory:
|
30
|
+
#
|
31
|
+
# :input [String] The path to the input file
|
32
|
+
# :output [String] The path to the output file or directory
|
33
|
+
#
|
34
|
+
# You may define one or several profiles to perform. If you provide a
|
35
|
+
# hash, all profile settings required for your recipe must be included.
|
36
|
+
#
|
37
|
+
# :profile [Hash] The configuration hash of one profile
|
38
|
+
# :profiles [Hash] Hashes of several profiles with namespace
|
39
|
+
#
|
40
|
+
# Single profile example:
|
41
|
+
#
|
42
|
+
# :profile => {
|
43
|
+
# :video_bit_rate => 110000,
|
44
|
+
# :dimensions => '240x160'
|
45
|
+
# }
|
46
|
+
#
|
47
|
+
# Multi-profile example:
|
48
|
+
#
|
49
|
+
# :profiles => {
|
50
|
+
# :low => {
|
51
|
+
# :video_bit_rate => 110000,
|
52
|
+
# :dimensions => '240x160'
|
53
|
+
# },
|
54
|
+
# :high => {
|
55
|
+
# :video_bit_rate => 800000,
|
56
|
+
# :dimensions => '600x400'
|
57
|
+
# }
|
58
|
+
# }
|
59
|
+
#
|
60
|
+
# If you have registered profiles for your encoder, you may refer to
|
61
|
+
# one or several profiles by providing its name. Without a profile, the
|
62
|
+
# default one will be performed.
|
63
|
+
# To register a profile, call YourEncoder.register_profile
|
64
|
+
#
|
65
|
+
# :profile [Symbol] The name of one profile
|
66
|
+
# :profiles [Array] A list of profile names
|
67
|
+
#
|
68
|
+
# @options [Hash] The configuration options
|
69
|
+
def initialize(options = {})
|
70
|
+
@options = options
|
71
|
+
[:tmp, :input, :output, :profile, :profiles].each do |attribute|
|
72
|
+
self.send("#{attribute}=", options[attribute]) if options[attribute]
|
73
|
+
end
|
74
|
+
set_default_options
|
75
|
+
end
|
76
|
+
|
77
|
+
# Perform the encoding workflow.
|
78
|
+
# All profiles will be performed in order. Lowest bit_rate first.
|
79
|
+
def run
|
80
|
+
validate_options
|
81
|
+
prepare
|
82
|
+
profiles.sorted.each do |@profile|
|
83
|
+
next unless process?
|
84
|
+
preprocess
|
85
|
+
process
|
86
|
+
postprocess
|
87
|
+
end
|
88
|
+
finish
|
89
|
+
end
|
90
|
+
|
91
|
+
# Fixed profile presets for this encoder.
|
92
|
+
# You may define profile presets inside your encoder class.
|
93
|
+
#
|
94
|
+
# When defining settings, you should define a :default setting as well
|
95
|
+
# to support single profile encodings.
|
96
|
+
# An example:
|
97
|
+
#
|
98
|
+
# {
|
99
|
+
# :low => {
|
100
|
+
# :video_bit_rate => 110000,
|
101
|
+
# :dimensions => '240x160'
|
102
|
+
# },
|
103
|
+
# :high => {
|
104
|
+
# :video_bit_rate => 800000,
|
105
|
+
# :dimensions => '600x400'
|
106
|
+
# }
|
107
|
+
# }.tap do |p|
|
108
|
+
# p[:default] = p[:high]
|
109
|
+
# end
|
110
|
+
def self.profile_presets; end
|
111
|
+
|
112
|
+
# Define a default file extension for your encoder.
|
113
|
+
# Or define one in a profile configuration.
|
114
|
+
def self.file_extension; end
|
115
|
+
|
116
|
+
class << self
|
117
|
+
attr_accessor :registered_profiles
|
118
|
+
@registered_profiles = {}
|
119
|
+
|
120
|
+
# Register a profile with given name and settings.
|
121
|
+
def register_profile(name, settings)
|
122
|
+
@registered_profiles[name] = settings
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
attr_reader :flags
|
129
|
+
|
130
|
+
# This method holds the recipe to perform.
|
131
|
+
# It is required that you define a custom encoding recipe inside your
|
132
|
+
# encoder class.
|
133
|
+
#
|
134
|
+
# The recipe must be a executable string that may contain placeholders
|
135
|
+
# that we call 'flags'. A simplified example:
|
136
|
+
#
|
137
|
+
# 'ffmpeg -i %{input} -threads 0 -y %{output}'
|
138
|
+
#
|
139
|
+
def recipe
|
140
|
+
raise(RecipeError, 'Please define an encoding recipe inside your encoder class')
|
141
|
+
end
|
142
|
+
|
143
|
+
# Handle the response returned from processing.
|
144
|
+
# Define a response handler for your recipe inside your encoder class.
|
145
|
+
#
|
146
|
+
# TODO: Example
|
147
|
+
def handle_response(stdout, stderr); end
|
148
|
+
|
149
|
+
# Set some default options.
|
150
|
+
def set_default_options
|
151
|
+
@profiles ||= Util::Profiles.new(:base => self)
|
152
|
+
@tmp ||= Util::Tmp.new(:base => self)
|
153
|
+
@flags ||= Util::Flags.new(:base => self)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Ensure that valid options are given.
|
157
|
+
# Please override this method if you need checks for custom arguments.
|
158
|
+
#
|
159
|
+
# By default, input, output, and profiles will be checked.
|
160
|
+
def validate_options
|
161
|
+
input ? input.validate : raise(InputError, 'No input defined')
|
162
|
+
output ? output.validate : raise(OutputError, 'No output defined')
|
163
|
+
profiles ? profiles.validate : raise(ProfileError, 'No profiles defined')
|
164
|
+
flags ? flags.validate : raise(FlagError, 'No flags defined')
|
165
|
+
end
|
166
|
+
|
167
|
+
# Prepare for encoding.
|
168
|
+
# Please override this method inside your encoder
|
169
|
+
# class, if you need custom preparation.
|
170
|
+
#
|
171
|
+
# Currently, the tmp folder will be created.
|
172
|
+
def prepare
|
173
|
+
tmp.make_dir
|
174
|
+
end
|
175
|
+
|
176
|
+
# Decide whether the current profile should be processed.
|
177
|
+
def process?
|
178
|
+
true
|
179
|
+
end
|
180
|
+
|
181
|
+
# Preprocess each encoding profile.
|
182
|
+
# Downsize video bit rate if it exceeds input bit rate.
|
183
|
+
def preprocess
|
184
|
+
if higher_bit_rate?
|
185
|
+
profile.settings[:video_bit_rate] = input.bit_rate
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Return true if wanted bit rate is higher than input's one.
|
190
|
+
# Allow 5% tolerance.
|
191
|
+
def higher_bit_rate?
|
192
|
+
input.bit_rate && profile.bit_rate * 1.05 > input.bit_rate
|
193
|
+
end
|
194
|
+
|
195
|
+
# Perform the encoding command.
|
196
|
+
# TODO: Describe.
|
197
|
+
def process
|
198
|
+
cmd = flags.render(recipe)
|
199
|
+
logger.info("\nEncoding profile #{profile.name}...\n#{cmd}\n")
|
200
|
+
pid, stdin, stdout, stderr = POSIX::Spawn::popen4(cmd)
|
201
|
+
handle_response(stdout, stderr)
|
202
|
+
ppid, status = Process::wait2(pid)
|
203
|
+
unless status.exitstatus == 0
|
204
|
+
raise(ProcessingError, "Execution failed:\n#{stderr.read}")
|
205
|
+
end
|
206
|
+
ensure # close all streams
|
207
|
+
[stdin, stdout, stderr].each { |io| io.close rescue nil }
|
208
|
+
end
|
209
|
+
|
210
|
+
# Postprocess each encoding profile.
|
211
|
+
def postprocess; end
|
212
|
+
|
213
|
+
# Hook for finishing touches.
|
214
|
+
# TODO: Describe.
|
215
|
+
def finish
|
216
|
+
encoded_files = output.copy_files
|
217
|
+
tmp.remove_dir
|
218
|
+
encoded_files
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
module Helper
|
4
|
+
|
5
|
+
# This helper provides methods for the encoder base class.
|
6
|
+
module Base
|
7
|
+
|
8
|
+
# Define setters for profile options.
|
9
|
+
# For either option, :profile or :profiles, a Util::Profiles object
|
10
|
+
# will be initialized.
|
11
|
+
[:profile, :profiles].each do |option|
|
12
|
+
class_eval <<-EOT
|
13
|
+
def #{option}=(settings)
|
14
|
+
raise(ArgumentError, "Nil is not allowed") unless settings
|
15
|
+
@profiles = Util::Profiles.new(
|
16
|
+
#{option.inspect} => settings, :base => self
|
17
|
+
)
|
18
|
+
end
|
19
|
+
EOT
|
20
|
+
end
|
21
|
+
|
22
|
+
# Define setters for other options.
|
23
|
+
# For each option a Util:: Object will be initialized, which are
|
24
|
+
# Util::Tmp, Util::Input, and Util::Output.
|
25
|
+
[:tmp, :input, :output].each do |option|
|
26
|
+
class_eval <<-EOT
|
27
|
+
def #{option}=(input)
|
28
|
+
raise(ArgumentError, "Nil is not allowed") unless input
|
29
|
+
util = Util::#{option.to_s.classify}
|
30
|
+
@#{option} = input.is_a?(util) ? input : util.new(:path => input, :base => self )
|
31
|
+
end
|
32
|
+
EOT
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO: DESCRIBE
|
36
|
+
def uuid
|
37
|
+
@uuid ||= Vidibus::Uuid.generate
|
38
|
+
end
|
39
|
+
|
40
|
+
# Reader for the logger.
|
41
|
+
def logger
|
42
|
+
Vidibus::Encoder.logger
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
module Helper
|
4
|
+
module Flags
|
5
|
+
|
6
|
+
# Register the inheritable reader +registered_flags+ on class level.
|
7
|
+
def self.extended(base)
|
8
|
+
base.class_eval <<-RUBY
|
9
|
+
unless defined?(@@registered_flags)
|
10
|
+
@@registered_flags = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.registered_flags
|
14
|
+
@@registered_flags
|
15
|
+
end
|
16
|
+
RUBY
|
17
|
+
end
|
18
|
+
|
19
|
+
# Register a flag handler. A flag handler will be called when
|
20
|
+
# rendering the encoding recipe if a matching profile setting is
|
21
|
+
# available.
|
22
|
+
#
|
23
|
+
# Usage:
|
24
|
+
#
|
25
|
+
# class MyEncoder < Vidibus::Encoder::Base
|
26
|
+
# flag(:active) { |value| "-v #{value}"}
|
27
|
+
# end
|
28
|
+
def flag(name, &block)
|
29
|
+
raise(ArgumentError, 'Block is missing') unless block_given?
|
30
|
+
registered_flags[name.to_sym] = block
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
module Helper
|
4
|
+
module Tools
|
5
|
+
|
6
|
+
# Return a matching frame rate from given list.
|
7
|
+
# You may use this method to determine which of your valid frame rates
|
8
|
+
# fits the input best.
|
9
|
+
def matching_frame_rate(list)
|
10
|
+
raise(ArgumentError, 'Argument must be an array') unless list && list.is_a?(Array)
|
11
|
+
input_frame_rate = input.frame_rate
|
12
|
+
list.each do |rate|
|
13
|
+
return rate if rate == input_frame_rate
|
14
|
+
end
|
15
|
+
# Detect the smallest multiple of any list entry
|
16
|
+
lowest_q = nil
|
17
|
+
wanted = nil
|
18
|
+
list.each do |rate|
|
19
|
+
q, r = input_frame_rate.divmod(rate)
|
20
|
+
if r == 0 && (!lowest_q || lowest_q > q)
|
21
|
+
lowest_q = q
|
22
|
+
wanted = rate
|
23
|
+
end
|
24
|
+
end
|
25
|
+
wanted
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
module Util
|
4
|
+
class Flags
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_accessor :base
|
8
|
+
|
9
|
+
# Initialize a flags object.
|
10
|
+
# One option is required to pass validation:
|
11
|
+
#
|
12
|
+
# :base [Vidibus::Encoder::Base] The encoder object
|
13
|
+
def initialize(options = {})
|
14
|
+
@base = options[:base]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Ensure that the base attribute is around.
|
18
|
+
def validate
|
19
|
+
raise(FlagError, 'Define a base class for flags') unless base
|
20
|
+
end
|
21
|
+
|
22
|
+
# This method turns the recipe into a command string by replacing all
|
23
|
+
# placeholders and removing empty ones.
|
24
|
+
#
|
25
|
+
# If a flag handler is defined for a placeholder and the profile
|
26
|
+
# setting is present, the flag handler will be called.
|
27
|
+
#
|
28
|
+
# Examples:
|
29
|
+
#
|
30
|
+
# base = Vidibus::Encoder::Base.new
|
31
|
+
# flags = Vidibus::Encoder::Util::Flags.new(:base => base)
|
32
|
+
# profile = Vidibus::Encoder::Util::Profile.new(:base => base)
|
33
|
+
# encoder.instance_variable_set('@profile', profile)
|
34
|
+
#
|
35
|
+
# recipe = 'some %{thing}'
|
36
|
+
#
|
37
|
+
# # Without a matching profile setting
|
38
|
+
# flags.render(recipe)
|
39
|
+
# # => 'some '
|
40
|
+
#
|
41
|
+
# # With a matching profile setting
|
42
|
+
# encoder.profile.settings[:thing] = 'beer'
|
43
|
+
# flags.render(recipe)
|
44
|
+
# # => 'some beer'
|
45
|
+
#
|
46
|
+
# # With a matching profile setting and flag handler
|
47
|
+
# encoder.profile.settings[:thing] = 'beer'
|
48
|
+
# encoder.class.flag(:thing) { |value| "cold #{value}" }
|
49
|
+
# flags.render(recipe)
|
50
|
+
# # => 'some cold beer'
|
51
|
+
def render(recipe)
|
52
|
+
recipe = recipe.gsub(/%\{([^\{]+)\}/) do |match|
|
53
|
+
flag = $1.to_sym
|
54
|
+
value = base.profile.try!(flag)
|
55
|
+
if value
|
56
|
+
if handler = base.class.registered_flags[flag]
|
57
|
+
match = base.instance_exec(value, &handler)
|
58
|
+
else
|
59
|
+
match = value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
match
|
63
|
+
end
|
64
|
+
recipe = render_input(recipe)
|
65
|
+
recipe = render_output(recipe)
|
66
|
+
cleanup(recipe)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Replace %{input} placeholder in recipe.
|
70
|
+
def render_input(recipe)
|
71
|
+
recipe % {:input => %("#{base.input}")}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Replace %{output} placeholder in recipe.
|
75
|
+
def render_output(recipe)
|
76
|
+
return recipe unless base.input && base.output
|
77
|
+
output = base.tmp.join(base.output.file_name)
|
78
|
+
recipe % {:output => %("#{output}")}
|
79
|
+
end
|
80
|
+
|
81
|
+
# Remove empty placeholders.
|
82
|
+
def cleanup(recipe)
|
83
|
+
recipe.gsub(/%\{[^\{]+\}/, '').gsub(/ +/, ' ')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
module Util
|
4
|
+
class Input
|
5
|
+
|
6
|
+
attr_accessor :path
|
7
|
+
attr_reader :properties
|
8
|
+
|
9
|
+
# Initialize an input object.
|
10
|
+
# One option is required:
|
11
|
+
#
|
12
|
+
# :path [String] The path to the input file
|
13
|
+
def initialize(options)
|
14
|
+
@path = options[:path]
|
15
|
+
set_properties!
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return the path.
|
19
|
+
def to_s
|
20
|
+
path
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return true if path is readable.
|
24
|
+
def readable?
|
25
|
+
File.readable?(path)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ensure that a path is given and readable.
|
29
|
+
def validate
|
30
|
+
readable? || raise(InputError, 'Input is not readable')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return aspect ratio of input file.
|
34
|
+
def aspect
|
35
|
+
@aspect ||= width/height.to_f
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Analyze file info of input and set properties.
|
41
|
+
# If analysis fails, a DataError will be raised.
|
42
|
+
def set_properties!
|
43
|
+
return unless present?
|
44
|
+
begin
|
45
|
+
@properties = Fileinfo(path)
|
46
|
+
rescue => error
|
47
|
+
end
|
48
|
+
@properties || raise(DataError, "Extracting input data failed!\n#{error}\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
# Try to return value from properties hash. Return nil if property is
|
52
|
+
# undefined or nil.
|
53
|
+
def method_missing(sym, *arguments)
|
54
|
+
if properties && value = properties[sym]
|
55
|
+
value
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
module Util
|
4
|
+
class Output
|
5
|
+
|
6
|
+
attr_accessor :path
|
7
|
+
attr_reader :base
|
8
|
+
|
9
|
+
# Initialize an output object.
|
10
|
+
# Two options are required:
|
11
|
+
#
|
12
|
+
# :base [Vidibus::Encoder::Base] The encoder object
|
13
|
+
# :path [String] The path to the output file or directory
|
14
|
+
def initialize(options)
|
15
|
+
@base = options[:base]
|
16
|
+
@path = options[:path]
|
17
|
+
make_dir
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return the output path.
|
21
|
+
def to_s
|
22
|
+
file_path || path
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return the directory name from path.
|
26
|
+
def dir
|
27
|
+
@dir ||= (!exist? || directory?) ? path : File.dirname(path)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Extract the file name from given path or input file.
|
31
|
+
def file_name
|
32
|
+
path[/([^\/]+\.[^\/]+)$/, 1] || begin
|
33
|
+
if base.input
|
34
|
+
base_name(base.input.path).tap do |name|
|
35
|
+
if base.profile
|
36
|
+
name << ".#{base.profile.file_extension}"
|
37
|
+
if base.profile.name && base.profile.name.to_s != 'default'
|
38
|
+
name.gsub!(/(\.[^\.]+)$/, "-#{base.profile.name}\\1")
|
39
|
+
end
|
40
|
+
else
|
41
|
+
raise(OutputError, 'Could not determine file name because the current profile does not define a file extension')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
raise(OutputError, 'Could not determine file name from input or output path')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def file_path
|
51
|
+
File.join(dir, file_name) if file_name
|
52
|
+
end
|
53
|
+
|
54
|
+
def base_name(str = file_name)
|
55
|
+
str[/([^\/]+)\.[^\.]+$/, 1]
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return true if a path has been defined.
|
59
|
+
def present?
|
60
|
+
!!path
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return true if path exists
|
64
|
+
def exist?
|
65
|
+
File.exist?(path)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return true if path is a directory
|
69
|
+
def directory?
|
70
|
+
File.directory?(path)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Ensure that a path is given.
|
74
|
+
def validate
|
75
|
+
present? || raise(OutputError, 'No output defined')
|
76
|
+
end
|
77
|
+
|
78
|
+
# Create output directory
|
79
|
+
def make_dir
|
80
|
+
FileUtils.mkdir_p(dir) unless exist?
|
81
|
+
end
|
82
|
+
|
83
|
+
# Copy files from tmp folder to output folder.
|
84
|
+
def copy_files
|
85
|
+
begin
|
86
|
+
files = Dir.glob("#{base.tmp}/*")
|
87
|
+
FileUtils.cp_r(files, dir)
|
88
|
+
files.each do |file|
|
89
|
+
file.gsub!(base.tmp.to_s, dir)
|
90
|
+
end
|
91
|
+
rescue => e
|
92
|
+
raise("Copying output files from #{base.tmp} to #{path} failed: #{e.message}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
module Util
|
4
|
+
class Profile
|
5
|
+
attr_accessor :name, :settings, :base
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@name = options[:name]
|
9
|
+
@settings = options[:settings] || {}
|
10
|
+
@base = options[:base]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Sum up audio and video bit_rate unless bit_rate
|
14
|
+
# has been defined in settings.
|
15
|
+
def bit_rate
|
16
|
+
settings[:bit_rate] || audio_bit_rate.to_i + video_bit_rate.to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
# Ensure that all required attribtues have been set.
|
20
|
+
def validate
|
21
|
+
raise(ProfileError, 'Define a name for this profile') if [nil, ''].include?(name)
|
22
|
+
raise(ProfileError, 'Define a settings hash for this profile') unless settings.is_a?(Hash) && settings.any?
|
23
|
+
raise(ProfileError, 'Define a encoder class for this profile') unless base
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return a list of all profile attributes
|
27
|
+
# including the given settings.
|
28
|
+
def attributes
|
29
|
+
@attributes ||= (settings.keys.map(&:to_s) + %w[width height dimensions]).sort.uniq
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return the width. If the wanted width exceeds the
|
33
|
+
# input's one, it will be scaled down.
|
34
|
+
#
|
35
|
+
# Define a modulus attribute to adjust dimensions. For best
|
36
|
+
# encoding results, the modulus should be 16. 8, 4, and
|
37
|
+
# even 2 will also work, but image quality increases with
|
38
|
+
# higher numbers because compression works better.
|
39
|
+
#
|
40
|
+
# @modulus [Integer] The modulus for rounding the value
|
41
|
+
#
|
42
|
+
# @return [Integer] The width
|
43
|
+
def width(modulus = 1)
|
44
|
+
@width ||= {}
|
45
|
+
@width[modulus] = dim(:width, modulus)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return the height. If the wanted height exceeds the
|
49
|
+
# input's one, it will be scaled down.
|
50
|
+
#
|
51
|
+
# Define a modulus attribute to adjust dimensions. For best
|
52
|
+
# encoding results, the modulus should be 16. 8, 4, and
|
53
|
+
# even 2 will also work, but image quality increases with
|
54
|
+
# higher numbers because compression works better.
|
55
|
+
#
|
56
|
+
# @modulus [Integer] The modulus for rounding the value
|
57
|
+
#
|
58
|
+
# @return [Integer] The height
|
59
|
+
def height(modulus = 1)
|
60
|
+
@height ||= {}
|
61
|
+
@height[modulus] = dim(:height, modulus)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return dimensions. If wanted dimensions exceed the input's
|
65
|
+
# ones, they will be scaled down.
|
66
|
+
#
|
67
|
+
# Define a modulus attribute to adjust dimensions. For best
|
68
|
+
# encoding results, the modulus should be 16. 8, 4, and
|
69
|
+
# even 2 will also work, but image quality increases with
|
70
|
+
# higher numbers because compression works better.
|
71
|
+
#
|
72
|
+
# @modulus [Integer] The modulus for rounding the value
|
73
|
+
#
|
74
|
+
# @return [String] The dimensions
|
75
|
+
def dimensions(modulus = 1)
|
76
|
+
@dimensions ||= {}
|
77
|
+
@dimensions[modulus] = begin
|
78
|
+
"#{width(modulus)}x#{height(modulus)}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Return the aspect ratio of width to height as string like "16:9".
|
83
|
+
# Define a modulus attribute to adjust dimensions.
|
84
|
+
#
|
85
|
+
# @modulus [Integer] The modulus for rounding the value
|
86
|
+
#
|
87
|
+
# @return [String] The dimensions
|
88
|
+
def aspect_ratio(modulus = 1)
|
89
|
+
@aspect_ratio ||= settings[:aspect_ratio] ||= begin
|
90
|
+
w = width(modulus)
|
91
|
+
h = height(modulus)
|
92
|
+
if w > 0 && h > 0
|
93
|
+
w/h.to_f
|
94
|
+
else
|
95
|
+
1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def file_extension
|
101
|
+
@file_extension ||= settings[:file_extension] || base.class.file_extension || raise(ProfileError, 'Define a file extension for this profile')
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Try to return value from settings hash. Return nil if setting is
|
107
|
+
# undefined or nil.
|
108
|
+
def method_missing(sym, *arguments)
|
109
|
+
if settings && value = settings[sym]
|
110
|
+
value
|
111
|
+
# elseif TODO: check if it's a setter
|
112
|
+
else
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Return the wanted dimension. If it exceeds the input's
|
118
|
+
# one, it will be scaled down.
|
119
|
+
#
|
120
|
+
# The dimension will be optained from one of
|
121
|
+
# the following sources:
|
122
|
+
# 1. from the value given in settings
|
123
|
+
# 2. from dimensions given in settings
|
124
|
+
# 3. calculated from opposite value, if given
|
125
|
+
# 4. from the input's properties
|
126
|
+
#
|
127
|
+
# @wanted [Symbol] The wanted dimension: :width or :height
|
128
|
+
# @modulus [Integer] The modulus for rounding the value
|
129
|
+
#
|
130
|
+
# @return [Integer] The width
|
131
|
+
def dim(wanted, modulus = 1)
|
132
|
+
modulus = modulus.to_i
|
133
|
+
|
134
|
+
w = (wanted == :width)
|
135
|
+
value = settings[wanted]
|
136
|
+
given = base.input.send(wanted).to_f
|
137
|
+
|
138
|
+
opposite = w ? :height : :width
|
139
|
+
_value = settings[opposite]
|
140
|
+
_given = base.input.send(opposite).to_f
|
141
|
+
|
142
|
+
if !value && settings[:dimensions]
|
143
|
+
matches = settings[:dimensions].match(/(\d+)x(\d+)/).to_a
|
144
|
+
i = w ? 1 : -1
|
145
|
+
value = matches[i]
|
146
|
+
_value ||= matches[-i]
|
147
|
+
end
|
148
|
+
|
149
|
+
if value
|
150
|
+
value = value.to_i
|
151
|
+
if value > given
|
152
|
+
value = given
|
153
|
+
end
|
154
|
+
if _value && _value.to_i > _given
|
155
|
+
value *= _given/_value.to_f
|
156
|
+
end
|
157
|
+
else
|
158
|
+
value = given
|
159
|
+
if _value && _value.to_i < _given
|
160
|
+
value *= _value.to_f/_given
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
if modulus > 1
|
165
|
+
q, r = value.to_i.divmod(modulus)
|
166
|
+
value = q * modulus
|
167
|
+
end
|
168
|
+
value.round
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
module Util
|
4
|
+
class Profiles
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :profile, :profiles, :base
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@base = options[:base]
|
11
|
+
@profile = options[:profile]
|
12
|
+
@profiles = options[:profiles]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return all profiles available for encoder base.
|
16
|
+
# For better encapsulation this method is placed here.
|
17
|
+
def available
|
18
|
+
@available ||= begin
|
19
|
+
(base.class.registered_profiles || {}).tap do |items|
|
20
|
+
items.merge!(base.class.profile_presets) if base.class.profile_presets
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return the used profile(s). If no profile is used, an empty hash
|
26
|
+
# will be returned.
|
27
|
+
#
|
28
|
+
# @return [Hash] A collection of profile objects
|
29
|
+
def collection
|
30
|
+
@collection ||= begin
|
31
|
+
begin
|
32
|
+
map
|
33
|
+
rescue ProfileError
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
alias :to_h :collection
|
39
|
+
|
40
|
+
# Iterate over the used profiles.
|
41
|
+
def each
|
42
|
+
collection.each do |profile|
|
43
|
+
yield(profile)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return the used profiles, sorted by given attribute.
|
48
|
+
#
|
49
|
+
# attribute [Hash] A collection of profile objects
|
50
|
+
#
|
51
|
+
# Default sorting attribute is :bit_rate.
|
52
|
+
def sorted(attribute = :bit_rate)
|
53
|
+
@sorted ||= {}
|
54
|
+
@sorted[attribute] ||= sort_by { |p| p.send(attribute) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return true if profile config is available, raise a ProfileError
|
58
|
+
# otherwise.
|
59
|
+
def validate
|
60
|
+
!!map || raise(ProfileError, 'No profiles defined')
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return true if several profiles are in use.
|
64
|
+
def multi?
|
65
|
+
@is_multi ||= used.count > 1
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Return a collection of mapped profile objects.
|
71
|
+
def map
|
72
|
+
@map ||= config.map do |name, settings|
|
73
|
+
Profile.new.tap do |profile|
|
74
|
+
profile.name = name
|
75
|
+
profile.settings = settings
|
76
|
+
profile.base = base
|
77
|
+
profile.validate
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Return a profile hash for any given profile.
|
83
|
+
def config
|
84
|
+
@config ||= multi_config || single_config
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return a config hash for wanted profiles.
|
88
|
+
def multi_config
|
89
|
+
if profiles.is_a?(Hash)
|
90
|
+
profiles
|
91
|
+
elsif profiles.is_a?(Array)
|
92
|
+
{}.tap do |p|
|
93
|
+
for name in profiles
|
94
|
+
p[name] = available[name] ||
|
95
|
+
raise(ProfileError, "Profile #{name.inspect} is undefined")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return a config hash for wanted profile.
|
102
|
+
def single_config
|
103
|
+
default = begin
|
104
|
+
if profile.is_a?(Hash)
|
105
|
+
profile
|
106
|
+
elsif [String, Symbol].include?(profile.class)
|
107
|
+
available[profile] ||
|
108
|
+
raise(ProfileError, "Profile #{profile.inspect} is undefined")
|
109
|
+
else
|
110
|
+
available[:default] ||
|
111
|
+
raise(ProfileError, 'No default profile defined')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
{:default => default}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Vidibus
|
2
|
+
module Encoder
|
3
|
+
module Util
|
4
|
+
class Tmp
|
5
|
+
DEFAULT = '/tmp/vidibus-encoder'
|
6
|
+
|
7
|
+
attr_reader :path, :base
|
8
|
+
|
9
|
+
# Initialize a tmp folder object.
|
10
|
+
# One option is required:
|
11
|
+
#
|
12
|
+
# :base [Vidibus::Encoder::Base] The encoder object
|
13
|
+
#
|
14
|
+
# One option is optional:
|
15
|
+
#
|
16
|
+
# :path [String] The path to the tmp folder
|
17
|
+
def initialize(options)
|
18
|
+
@base = options[:base]
|
19
|
+
@path = File.join(options[:path] || DEFAULT, base.uuid)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return the default path.
|
23
|
+
def to_s
|
24
|
+
path
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return a path with additional arguments.
|
28
|
+
def join(*args)
|
29
|
+
File.join(path, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Make a temporary folder.
|
33
|
+
def make_dir
|
34
|
+
FileUtils.mkdir_p(path)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Remove the temporary folder.
|
38
|
+
def remove_dir
|
39
|
+
FileUtils.remove_dir(path) if File.exist?(path) && path.length > 3
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'vidibus/encoder/helper'
|
2
|
+
require 'vidibus/encoder/util'
|
3
|
+
require 'vidibus/encoder/base'
|
4
|
+
|
5
|
+
module Vidibus
|
6
|
+
module Encoder
|
7
|
+
extend self
|
8
|
+
|
9
|
+
class Error < StandardError; end
|
10
|
+
class ProcessingError < Error; end
|
11
|
+
class DataError < Error; end
|
12
|
+
class ConfigurationError < Error; end
|
13
|
+
class InputError < ConfigurationError; end
|
14
|
+
class OutputError < ConfigurationError; end
|
15
|
+
class ProfileError < ConfigurationError; end
|
16
|
+
class RecipeError < ConfigurationError; end
|
17
|
+
class FlagError < ConfigurationError; end
|
18
|
+
|
19
|
+
attr_accessor :formats
|
20
|
+
@formats = {}
|
21
|
+
|
22
|
+
# Register a new encoder format.
|
23
|
+
def register_format(name, processor)
|
24
|
+
unless processor.new.is_a?(Vidibus::Encoder::Base)
|
25
|
+
raise(ArgumentError, 'The processor must inherit Vidibus::Encoder::Base')
|
26
|
+
end
|
27
|
+
@formats ||= {}
|
28
|
+
@formats[name] = processor
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return the custom or standard logger.
|
32
|
+
# If Rails is around, Rails.logger will be used
|
33
|
+
# by default.
|
34
|
+
def logger
|
35
|
+
@logger ||= begin
|
36
|
+
defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set a custom logger instance.
|
41
|
+
def logger=(instance)
|
42
|
+
@logger = instance
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vidibus-encoder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- "Andr\xC3\xA9 Pankratz"
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-04-20 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: posix-spawn
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: vidibus-fileinfo
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 23
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 0
|
47
|
+
- 0
|
48
|
+
version: 1.0.0
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: vidibus-uuid
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: bundler
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 23
|
74
|
+
segments:
|
75
|
+
- 1
|
76
|
+
- 0
|
77
|
+
- 0
|
78
|
+
version: 1.0.0
|
79
|
+
type: :development
|
80
|
+
version_requirements: *id004
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rake
|
83
|
+
prerelease: false
|
84
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
type: :development
|
94
|
+
version_requirements: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: rdoc
|
97
|
+
prerelease: false
|
98
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
type: :development
|
108
|
+
version_requirements: *id006
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: rcov
|
111
|
+
prerelease: false
|
112
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
version: "0"
|
121
|
+
type: :development
|
122
|
+
version_requirements: *id007
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: rspec
|
125
|
+
prerelease: false
|
126
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
hash: 7
|
132
|
+
segments:
|
133
|
+
- 2
|
134
|
+
version: "2"
|
135
|
+
type: :development
|
136
|
+
version_requirements: *id008
|
137
|
+
- !ruby/object:Gem::Dependency
|
138
|
+
name: rr
|
139
|
+
prerelease: false
|
140
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
hash: 3
|
146
|
+
segments:
|
147
|
+
- 0
|
148
|
+
version: "0"
|
149
|
+
type: :development
|
150
|
+
version_requirements: *id009
|
151
|
+
description: Encoder framework
|
152
|
+
email: andre@vidibus.com
|
153
|
+
executables: []
|
154
|
+
|
155
|
+
extensions: []
|
156
|
+
|
157
|
+
extra_rdoc_files: []
|
158
|
+
|
159
|
+
files:
|
160
|
+
- lib/vidibus/encoder/base.rb
|
161
|
+
- lib/vidibus/encoder/helper/base.rb
|
162
|
+
- lib/vidibus/encoder/helper/flags.rb
|
163
|
+
- lib/vidibus/encoder/helper/tools.rb
|
164
|
+
- lib/vidibus/encoder/helper.rb
|
165
|
+
- lib/vidibus/encoder/util/flags.rb
|
166
|
+
- lib/vidibus/encoder/util/input.rb
|
167
|
+
- lib/vidibus/encoder/util/output.rb
|
168
|
+
- lib/vidibus/encoder/util/profile.rb
|
169
|
+
- lib/vidibus/encoder/util/profiles.rb
|
170
|
+
- lib/vidibus/encoder/util/tmp.rb
|
171
|
+
- lib/vidibus/encoder/util.rb
|
172
|
+
- lib/vidibus/encoder/version.rb
|
173
|
+
- lib/vidibus/encoder.rb
|
174
|
+
- lib/vidibus-encoder.rb
|
175
|
+
- LICENSE
|
176
|
+
- README.md
|
177
|
+
- Rakefile
|
178
|
+
has_rdoc: true
|
179
|
+
homepage: https://github.com/vidibus/vidibus-encoder
|
180
|
+
licenses: []
|
181
|
+
|
182
|
+
post_install_message:
|
183
|
+
rdoc_options: []
|
184
|
+
|
185
|
+
require_paths:
|
186
|
+
- lib
|
187
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
188
|
+
none: false
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
hash: 3
|
193
|
+
segments:
|
194
|
+
- 0
|
195
|
+
version: "0"
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
|
+
none: false
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
hash: 23
|
202
|
+
segments:
|
203
|
+
- 1
|
204
|
+
- 3
|
205
|
+
- 6
|
206
|
+
version: 1.3.6
|
207
|
+
requirements: []
|
208
|
+
|
209
|
+
rubyforge_project: vidibus-encoder
|
210
|
+
rubygems_version: 1.3.7
|
211
|
+
signing_key:
|
212
|
+
specification_version: 3
|
213
|
+
summary: Encoder framework
|
214
|
+
test_files: []
|
215
|
+
|