vidibus-encoder 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|