ffmpeg-filter_graph 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +3 -0
- data/README.md +138 -0
- data/Rakefile +10 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/ffmpeg-filter_graph.gemspec +32 -0
- data/lib/ffmpeg/filter_graph.rb +22 -0
- data/lib/ffmpeg/filter_graph/chain.rb +21 -0
- data/lib/ffmpeg/filter_graph/filter.rb +66 -0
- data/lib/ffmpeg/filter_graph/filter_factory.rb +80 -0
- data/lib/ffmpeg/filter_graph/filters/audio.rb +0 -0
- data/lib/ffmpeg/filter_graph/generate_filters_audio.rb +177 -0
- data/lib/ffmpeg/filter_graph/generate_filters_audio_sink.rb +11 -0
- data/lib/ffmpeg/filter_graph/generate_filters_audio_src.rb +19 -0
- data/lib/ffmpeg/filter_graph/generate_filters_multimedia.rb +14 -0
- data/lib/ffmpeg/filter_graph/generate_filters_video.rb +7 -0
- data/lib/ffmpeg/filter_graph/generate_filters_video_src.rb +10 -0
- data/lib/ffmpeg/filter_graph/graph.rb +22 -0
- data/lib/ffmpeg/filter_graph/helper.rb +32 -0
- data/lib/ffmpeg/filter_graph/utils/strings.rb +14 -0
- data/lib/ffmpeg/filter_graph/version.rb +5 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c05fb4a2ed62cd57c57bcd8d9bfbba450075712d
|
4
|
+
data.tar.gz: c9fdd4f24fcb014992c605e7a966c751965a92f0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 61591d28787f1495c3626879772fad0e6e1a5afbc1107cf58c5de427b76f83c44d74e3e26c4f155008f5784f3d416ccc196e47c2048fde57a267c435ffbe7c78
|
7
|
+
data.tar.gz: 87ccab1f5766a0103fdf4e468a12988dba2345eaacbecdfc3ad4b8403bc9ddbe00e866111a7a3e75a80c0e29ee15a594c55b947d0effdbfb487028d4f57cdb1a
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# Ffmpeg::FilterGraph
|
2
|
+
|
3
|
+
**Warning**: This gem is alpha as it gets. Use at your own peril.
|
4
|
+
|
5
|
+
The purpose of this gem is to help you create complex filter-graphs for FFmpeg.
|
6
|
+
In a sense, this gem is really a "string factory", as it's main output is a
|
7
|
+
single string you can pass as the argument to `ffmpeg`'s `-filter_complex`
|
8
|
+
command-line argument.
|
9
|
+
|
10
|
+
|
11
|
+
## Wishlist
|
12
|
+
- Track inpads and outpats, to ensure that outpads are used, and inpads exist.
|
13
|
+
- Support abbreviated option names
|
14
|
+
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'ffmpeg-filter_graph'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
$ bundle
|
27
|
+
|
28
|
+
Or install it yourself as:
|
29
|
+
|
30
|
+
$ gem install ffmpeg-filter_graph
|
31
|
+
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# This class adds an additional audio stream to a given multimedia file. The
|
37
|
+
# new audio stream will contain a copy of the film's soundtrack, with the
|
38
|
+
# commentary track mixed into the rear speakers (for a nice Statler and Waldorf
|
39
|
+
# experience). To make the commentary more audible, with "duck" the soundtrack
|
40
|
+
# before mixing in the commentary.
|
41
|
+
class AddCommentary
|
42
|
+
include FFmpeg::Filters::Helper
|
43
|
+
|
44
|
+
# Surround-sound prefixes
|
45
|
+
Soundtrack = 'c'
|
46
|
+
DuckedSoundtrack = 'r'
|
47
|
+
|
48
|
+
# ffmpeg notation
|
49
|
+
SoundtrackVideo = '1:v'
|
50
|
+
SoundtrackAudio = '1:a'
|
51
|
+
CommentaryAudio = '2:a'
|
52
|
+
|
53
|
+
def initialize(media_container_path, commentary_audio_path, output_path)
|
54
|
+
@media_container_path = media_container_path
|
55
|
+
@commentary_audio_path = commentary_audio_path
|
56
|
+
@output_path = output_path
|
57
|
+
end
|
58
|
+
|
59
|
+
def call
|
60
|
+
filter_graph = create_filter_graph
|
61
|
+
|
62
|
+
spawn('ffmpeg', '-i', @media_container_path, '-i', @commentary_audio_path,
|
63
|
+
'-filter_complex', filter_graph.to_s,
|
64
|
+
'-map', SoundtrackVideo, '-map', SoundtrackAudio,
|
65
|
+
'-map', "[#{filter_graph.outputs.first}]",
|
66
|
+
@output_path)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def create_filter_graph
|
72
|
+
graph(
|
73
|
+
outputs: 'CommentaryTrack',
|
74
|
+
|
75
|
+
chains: [
|
76
|
+
# We need two copies for the mix, and one copy for the audo-ducking
|
77
|
+
chain(
|
78
|
+
inputs: [CommentaryAudio],
|
79
|
+
filters: a_split(number: 3),
|
80
|
+
outputs: %w(RLcom RRcom Ducker)
|
81
|
+
),
|
82
|
+
|
83
|
+
# Auto-duck the soundtrack, so we can hear the commentary
|
84
|
+
chain(
|
85
|
+
inputs: [SoundtrackAudio, 'Ducker'],
|
86
|
+
filters: [
|
87
|
+
side_chain_compress(attack: 50, release: 200),
|
88
|
+
channel_split(channel_layout: '5.1')
|
89
|
+
],
|
90
|
+
outputs: surround_channels(DuckedSoundtrack),
|
91
|
+
),
|
92
|
+
|
93
|
+
# Merge commentary into rear channels
|
94
|
+
chain(
|
95
|
+
inputs: surround_channels(DuckedSoundtrack, :rl) << 'RLcom',
|
96
|
+
filters: mono_mix,
|
97
|
+
outputs: %w(RLmix)
|
98
|
+
),
|
99
|
+
chain(
|
100
|
+
inputs: surround_channels(DuckedSoundtrack, :rr) << 'RRcom',
|
101
|
+
filters: mono_mix,
|
102
|
+
outputs: %w(RRmix)
|
103
|
+
),
|
104
|
+
chain(
|
105
|
+
inputs: surround_channels(DuckedSoundtrack, :fl, :fr, :fc, :lfe) + %w(RLmix RRmix),
|
106
|
+
filters: [a_merge(inputs: 6)]
|
107
|
+
),
|
108
|
+
]
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
# You can easily encapsulate a group of filters with private methods, for
|
113
|
+
# better readability.
|
114
|
+
def mono_mix
|
115
|
+
[a_merge, pan(channel_layout: 'mono', outputs: ["c0 = c0 + c1"])]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
|
121
|
+
## Development
|
122
|
+
|
123
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
124
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
125
|
+
prompt that will allow you to experiment.
|
126
|
+
|
127
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
128
|
+
release a new version, update the version number in `version.rb`, and then run
|
129
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
130
|
+
git commits and tags, and push the `.gem` file to
|
131
|
+
[rubygems.org](https://rubygems.org).
|
132
|
+
|
133
|
+
|
134
|
+
## Contributing
|
135
|
+
|
136
|
+
Bug reports and pull requests are welcome on GitHub at
|
137
|
+
https://github.com/sangster/ffmpeg-filter_graph.
|
138
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ffmpeg/filter_graph/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'ffmpeg-filter_graph'
|
8
|
+
spec.version = Ffmpeg::FilterGraph::VERSION
|
9
|
+
spec.authors = ['Jon Sangster']
|
10
|
+
spec.email = ['jon@ertt.ca']
|
11
|
+
|
12
|
+
spec.summary = %q{FFmpeg filter-graph generator}
|
13
|
+
spec.description = %q{This gem generates complex filter-graphs for use with the ffmpeg application. ie: Values for the -filter_complex command-line option.}
|
14
|
+
spec.homepage = 'https://github.com/sangster/ffmpeg-filter_graph'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = 'exe'
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
25
|
+
spec.add_development_dependency 'minitest-reporters', '~> 1.1.8'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'guard', '~> 2.13.0'
|
28
|
+
spec.add_development_dependency 'guard-minitest', '~> 2.4.4'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'yard', '~> 0.7'
|
31
|
+
spec.add_development_dependency 'rdoc', '~> 3.12'
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module FFmpeg
|
2
|
+
module FilterGraph
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'ffmpeg/filter_graph/utils/strings'
|
7
|
+
|
8
|
+
require 'ffmpeg/filter_graph/helper'
|
9
|
+
|
10
|
+
require 'ffmpeg/filter_graph/filter'
|
11
|
+
require 'ffmpeg/filter_graph/filter_factory'
|
12
|
+
require 'ffmpeg/filter_graph/chain'
|
13
|
+
require 'ffmpeg/filter_graph/graph'
|
14
|
+
|
15
|
+
require 'ffmpeg/filter_graph/generate_filters_audio'
|
16
|
+
require 'ffmpeg/filter_graph/generate_filters_audio_sink'
|
17
|
+
require 'ffmpeg/filter_graph/generate_filters_audio_src'
|
18
|
+
require 'ffmpeg/filter_graph/generate_filters_multimedia'
|
19
|
+
require 'ffmpeg/filter_graph/generate_filters_video'
|
20
|
+
require 'ffmpeg/filter_graph/generate_filters_video_src'
|
21
|
+
|
22
|
+
require 'ffmpeg/filter_graph/version'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module FFmpeg::FilterGraph
|
2
|
+
class Chain
|
3
|
+
attr_accessor :inputs, :outputs, :filters
|
4
|
+
|
5
|
+
def initialize(inputs: [], outputs: [], filters: [])
|
6
|
+
self.inputs = inputs
|
7
|
+
self.outputs = outputs
|
8
|
+
self.filters = Array(filters).flatten.compact
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#{join(inputs)} #{filters.map(&:to_s).join(', ')} #{join(outputs)}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def join(arr)
|
18
|
+
arr.map { |i| "[#{i}]" }.join
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module FFmpeg::FilterGraph
|
2
|
+
class Filter
|
3
|
+
# @param name [String] Sets the filter-name output by this filter
|
4
|
+
def self.name(name = nil)
|
5
|
+
if name.nil?
|
6
|
+
@name || fail('filter name not set')
|
7
|
+
else
|
8
|
+
@name = name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param opts [Array<String>] a list of valid options for this filter
|
13
|
+
def self.options(*opts)
|
14
|
+
@opts ||= []
|
15
|
+
|
16
|
+
if opts.any?
|
17
|
+
@opts.concat(opts)
|
18
|
+
attr_accessor *opts
|
19
|
+
end
|
20
|
+
|
21
|
+
@opts
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
if options.any?
|
26
|
+
"#{self.class.name}=#{options_string}"
|
27
|
+
else
|
28
|
+
self.class.name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] A string concatenating the set options.
|
33
|
+
# @note May be overridden by atypical filters
|
34
|
+
def options_string
|
35
|
+
join_options(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set all options to nil
|
39
|
+
def empty!
|
40
|
+
self.class.options.each { |opt| send("#{opt}=", nil) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Hash<String,String>] a map of all options and their values. nil
|
44
|
+
# values will not be included.
|
45
|
+
def options
|
46
|
+
ret = {}
|
47
|
+
self.class.options.each do |opt|
|
48
|
+
if (val = self.send(opt))
|
49
|
+
ret[opt] = val
|
50
|
+
end
|
51
|
+
end
|
52
|
+
ret
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
# @param keys [Array<Symbol>] the names of the options to join together
|
58
|
+
# @return [String, nil] a string concatenating all options, in the format of
|
59
|
+
# "k1=v1:k2=v2:k3=v3". Will return nil of there are no set values
|
60
|
+
def join_options(*keys)
|
61
|
+
opts = options
|
62
|
+
parts = keys.map { |k| "#{k}=#{opts[k]}" if opts[k] }.compact
|
63
|
+
parts.join(':') if parts.any?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module FFmpeg::FilterGraph
|
2
|
+
# This factory creates a new [Filter] subclass. The filters are all very
|
3
|
+
# silimar, so it's easier to generate each class than to write them by hand.
|
4
|
+
class FilterFactory
|
5
|
+
include Utils::Strings
|
6
|
+
|
7
|
+
attr_accessor :class_name, :required, :optional, :editable, :options_string
|
8
|
+
|
9
|
+
def self.create(name, opts)
|
10
|
+
new(
|
11
|
+
name,
|
12
|
+
opts[:required],
|
13
|
+
opts[:optional],
|
14
|
+
opts[:editable],
|
15
|
+
&opts[:options_string]
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param class_name [#to_s] the name of the class to create
|
20
|
+
# @param required [nil, Array<String>] an optional list of the filter
|
21
|
+
# options which must be passed to its constructor
|
22
|
+
# @param optional [nil, Array<String>] an optional list of the filter
|
23
|
+
# options which are not required by the filter
|
24
|
+
# @param editable [Bool] if this filter supports "Timeline editing"
|
25
|
+
# @param options_string [Block] An optional callback to override the
|
26
|
+
# default method of constructing the string of options in the filter's
|
27
|
+
# output.
|
28
|
+
# @see Filter#options_string
|
29
|
+
def initialize(class_name, required, optional, editable, &options_string)
|
30
|
+
self.class_name = class_name.to_s
|
31
|
+
self.required = required || []
|
32
|
+
self.optional = optional || []
|
33
|
+
self.options_string = options_string
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param mod [Module] the module to create the new class in
|
37
|
+
# @param helper_module [Module] an optional module to create a helper-method
|
38
|
+
# in. ex: if the filter class is named MyFilter, a method will be created
|
39
|
+
# in the form of Helper.my_filter(*args); MyFilter.new(*args) end
|
40
|
+
def create_class_in(mod, helper_module: Helper)
|
41
|
+
# We need to make these local vars, to work in the Class.new block
|
42
|
+
cn = class_name.to_s
|
43
|
+
|
44
|
+
mod.const_set(cn, create_class(cn, required, optional, options_string))
|
45
|
+
|
46
|
+
if editable
|
47
|
+
end
|
48
|
+
|
49
|
+
if helper_module
|
50
|
+
helper_name = underscore(cn)
|
51
|
+
|
52
|
+
helper_module.module_exec do
|
53
|
+
klass = mod.const_get(cn)
|
54
|
+
define_method(helper_name) { |*args| klass.new(*args) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def create_class(cn, req, opt, opt_str)
|
62
|
+
Class.new(Filter) do
|
63
|
+
const_set('REQUIRED', req.freeze)
|
64
|
+
const_set('OPTIONAL', opt.freeze)
|
65
|
+
|
66
|
+
name(cn.downcase.to_sym)
|
67
|
+
options(*(const_get('REQUIRED') + const_get('OPTIONAL')))
|
68
|
+
|
69
|
+
def initialize(args = {})
|
70
|
+
self.class.const_get('REQUIRED').each do |o|
|
71
|
+
fail ArgumentError, "missing keyword: #{o}" unless args.key?(o)
|
72
|
+
end
|
73
|
+
args.each { |k, v| send("#{k}=", v) }
|
74
|
+
end
|
75
|
+
|
76
|
+
define_method(:options_string, &opt_str) if opt_str
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
File without changes
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# @see https://ffmpeg.org/ffmpeg-filters.html
|
2
|
+
module FFmpeg::FilterGraph
|
3
|
+
{
|
4
|
+
ACompressor: {
|
5
|
+
optional: [:level_in, :threshold, :ratio, :attack, :release, :makeup,
|
6
|
+
:knee, :link, :detection, :mix]
|
7
|
+
},
|
8
|
+
ACrossFade: {
|
9
|
+
required: [:curve1, :curve2],
|
10
|
+
optional: [:nb_samples, :duration, :overlap]
|
11
|
+
},
|
12
|
+
ADelay: {
|
13
|
+
required: [:delays]
|
14
|
+
},
|
15
|
+
AEcho: {
|
16
|
+
required: [:in_gain, :out_gain, :delays, :decays]
|
17
|
+
},
|
18
|
+
AEmphasis: {
|
19
|
+
required: [:level_in, :level_out, :type],
|
20
|
+
optional: [:mode]
|
21
|
+
},
|
22
|
+
AEval: {
|
23
|
+
required: [:exprs],
|
24
|
+
optional: [:channel_layout],
|
25
|
+
options_string: -> { [exprs, join_options(:channel_layout)].compact.join(':') }
|
26
|
+
},
|
27
|
+
AFade: {
|
28
|
+
required: [:curve],
|
29
|
+
optional: [:type, :start_sample, :nb_samples, :start_time, :duration]
|
30
|
+
},
|
31
|
+
AffTFilt: {
|
32
|
+
optional: [:real, :imag, :win_size, :win_func, :overlap]
|
33
|
+
},
|
34
|
+
AFormat: {
|
35
|
+
optional: [:channel_layouts, :sample_fmts, :sample_rates]
|
36
|
+
},
|
37
|
+
AGate: {
|
38
|
+
optional: [:level_in, :range, :threshold, :ratio, :attack, :release,
|
39
|
+
:makeup, :knee, :detection, :line]
|
40
|
+
},
|
41
|
+
ALimiter: {
|
42
|
+
optional: [:level_in, :level_out, :limit, :attack, :release, :asc,
|
43
|
+
:asc_level, :level]
|
44
|
+
},
|
45
|
+
AllPass: {
|
46
|
+
required: [:frequency, :width_type, :width]
|
47
|
+
},
|
48
|
+
AMerge: {
|
49
|
+
optional: [:inputs]
|
50
|
+
},
|
51
|
+
AMix: {
|
52
|
+
optional: [:inputs, :duration, :dropout_transition]
|
53
|
+
},
|
54
|
+
APad: {
|
55
|
+
optional: [:packet_size, :pad_len, :whole_len]
|
56
|
+
},
|
57
|
+
APhaser: {
|
58
|
+
optional: [:in_gain, :out_gain, :delay, :decay, :speed, :type]
|
59
|
+
},
|
60
|
+
APulsator: {
|
61
|
+
required: [:amount],
|
62
|
+
optional: [:level_in, :level_out, :mode, :offset_l, :offset_r, :width,
|
63
|
+
:timing, :bpm, :ms, :hz]
|
64
|
+
},
|
65
|
+
|
66
|
+
# TODO AResample: { },
|
67
|
+
|
68
|
+
ASetNSamples: {
|
69
|
+
optional: [:nb_out_samples, :pad]
|
70
|
+
},
|
71
|
+
ASetRate: {
|
72
|
+
optional: [:sample_rate]
|
73
|
+
},
|
74
|
+
AShowInfo: {},
|
75
|
+
AStats: {
|
76
|
+
optional: [:length, :metadata, :reset]
|
77
|
+
},
|
78
|
+
ASyncTs: {
|
79
|
+
optional: [:compensate, :min_delta, :first_pts]
|
80
|
+
},
|
81
|
+
ATempo: {
|
82
|
+
optional: [:tempo],
|
83
|
+
options_string: -> { tempo }
|
84
|
+
},
|
85
|
+
|
86
|
+
# TODO ATrim: {},
|
87
|
+
|
88
|
+
BandPass: {
|
89
|
+
required: [:width_type, :width],
|
90
|
+
optional: [:frequency, :csg]
|
91
|
+
},
|
92
|
+
BandReject: {
|
93
|
+
required: [:width_type, :width],
|
94
|
+
optional: [:frequency]
|
95
|
+
},
|
96
|
+
Bass: {
|
97
|
+
required: [:gain, :width_type, :width],
|
98
|
+
optional: [:frequency]
|
99
|
+
},
|
100
|
+
BiQuad: {
|
101
|
+
required: [:b0, :b1, :b2, :a0, :a1, :a2]
|
102
|
+
},
|
103
|
+
Bs2b: {
|
104
|
+
required: [:profile, :fcut, :feed]
|
105
|
+
},
|
106
|
+
ChannelMap: {
|
107
|
+
required: [:map],
|
108
|
+
optional: [:channel_layout]
|
109
|
+
},
|
110
|
+
ChannelSplit: {
|
111
|
+
optional: [:channel_layout]
|
112
|
+
},
|
113
|
+
Chorus: {
|
114
|
+
required: [:delays, :decays, :speeds, :depths],
|
115
|
+
optional: [:in_gain, :out_gain]
|
116
|
+
},
|
117
|
+
|
118
|
+
# TODO Compand
|
119
|
+
# TODO CompensationDelay
|
120
|
+
# TODO DcShift
|
121
|
+
# TODO DynAudNorm
|
122
|
+
|
123
|
+
Earwax: {},
|
124
|
+
|
125
|
+
# TODO Equalizer
|
126
|
+
# TODO ExtraStereo
|
127
|
+
# TODO FirEqualizer
|
128
|
+
# TODO Flanger
|
129
|
+
# TODO HighPass
|
130
|
+
# TODO Join
|
131
|
+
# TODO Ladspa
|
132
|
+
# TODO LowPass
|
133
|
+
|
134
|
+
Pan: {
|
135
|
+
optional: [:channel_layout, :outputs],
|
136
|
+
options_string: -> { "#{channel_layout}| #{outputs.join(' | ')}" }
|
137
|
+
},
|
138
|
+
ReplayGain: {},
|
139
|
+
Resample: {},
|
140
|
+
|
141
|
+
# TODO RubberBand
|
142
|
+
|
143
|
+
SideChainCompress: {
|
144
|
+
optional: [:level_in, :threshold, :ratio, :attack, :release, :makeup,
|
145
|
+
:knee, :link, :detection, :level_sc, :mix]
|
146
|
+
},
|
147
|
+
SideChainGate: {
|
148
|
+
optional: [:level_in, :range, :threshold, :ratio, :attack, :release,
|
149
|
+
:makeup, :knee, :link, :detection, :level_sc]
|
150
|
+
},
|
151
|
+
SilenceDetect: {
|
152
|
+
optional: [:duration, :noise]
|
153
|
+
},
|
154
|
+
|
155
|
+
# TODO SilenceRemove
|
156
|
+
# TODO Sofalizer
|
157
|
+
# TODO StereoTools
|
158
|
+
# TODO StereoWiden
|
159
|
+
# TODO Scale_Npp
|
160
|
+
# TODO Select
|
161
|
+
# TODO Treble
|
162
|
+
|
163
|
+
Tremolo: {
|
164
|
+
optional: [:f, :d]
|
165
|
+
},
|
166
|
+
Vibrato: {
|
167
|
+
optional: [:f, :d]
|
168
|
+
},
|
169
|
+
Volume: {
|
170
|
+
editable: true,
|
171
|
+
optional: [:volume, :precision, :replaygain, :replaygain_preamp, :eval]
|
172
|
+
},
|
173
|
+
VolumeDetect: {},
|
174
|
+
}.each do |class_name, opts|
|
175
|
+
FilterFactory.create(class_name, opts).create_class_in(self)
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# @see https://ffmpeg.org/ffmpeg-filters.html
|
2
|
+
module FFmpeg::FilterGraph
|
3
|
+
{
|
4
|
+
# TODO ABuffer
|
5
|
+
AEvalSrc: {
|
6
|
+
required: [:exprs],
|
7
|
+
optional: [:sample_rate, :duration, :channel_layout, :nb_samples],
|
8
|
+
options_string: -> { "#{exprs}:#{join_options(:duration, :sample_rate, :channel_layout, :nb_samples)}" }
|
9
|
+
},
|
10
|
+
ANullSrc: {
|
11
|
+
optional: [:channel_layout, :sample_rate, :nb_samples]
|
12
|
+
},
|
13
|
+
# TODO Flite
|
14
|
+
# TODO ANoiseSrc
|
15
|
+
# TODO Sine
|
16
|
+
}.each do |class_name, opts|
|
17
|
+
FilterFactory.create(class_name, opts).create_class_in(self)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# @see https://ffmpeg.org/ffmpeg-filters.html
|
2
|
+
module FFmpeg::FilterGraph
|
3
|
+
{
|
4
|
+
Concat: {
|
5
|
+
optional: [:n, :v, :a]
|
6
|
+
},
|
7
|
+
ASplit: {
|
8
|
+
optional: [:number],
|
9
|
+
options_string: -> { number.to_s }
|
10
|
+
},
|
11
|
+
}.each do |class_name, opts|
|
12
|
+
FilterFactory.create(class_name, opts).create_class_in(self)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module FFmpeg::FilterGraph
|
2
|
+
class FilterGraph
|
3
|
+
attr_accessor :chains, :outputs
|
4
|
+
|
5
|
+
def initialize(chains: [], outputs: [])
|
6
|
+
self.chains = chains
|
7
|
+
self.outputs = outputs
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_outputs(*pads)
|
11
|
+
self.outputs.push(*pads.map(&:to_s))
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
[
|
16
|
+
chains.map(&:to_s).join('; '),
|
17
|
+
outputs.map { |o| "[#{o}]" }.join(''),
|
18
|
+
].join('')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module FFmpeg::FilterGraph
|
2
|
+
module Helper
|
3
|
+
CHANNELS = %w(FL FR FC LFE RL RR)
|
4
|
+
|
5
|
+
def graph(*args); Graph.new(*args) end
|
6
|
+
def chain(*args); Chain.new(*args) end
|
7
|
+
|
8
|
+
# Filter-specific helpers will be added to this module by default, by the
|
9
|
+
# FilterFactory class.
|
10
|
+
|
11
|
+
def surround_channels(prefix = '', *channels)
|
12
|
+
channels = CHANNELS if channels.empty?
|
13
|
+
channels.map(&:to_s).map(&:upcase).map { |c| "#{prefix}#{c}" }
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return the number of channels in the given channel layout
|
17
|
+
def count_channels(ch)
|
18
|
+
case ch
|
19
|
+
when Fixnum then ch
|
20
|
+
when '7.1' then 8
|
21
|
+
when '6.1' then 7
|
22
|
+
when '5.1' then 6
|
23
|
+
when '4.1' then 5
|
24
|
+
when '3.1' then 4
|
25
|
+
when '2.1' then 3
|
26
|
+
when 'stereo' then 2
|
27
|
+
when 'mono' then 1
|
28
|
+
else fail 'unknown layout'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module FFmpeg::FilterGraph::Utils
|
2
|
+
module Strings
|
3
|
+
# From github: activesupport/lib/active_support/inflector/methods.rb
|
4
|
+
def underscore(camel_cased_word)
|
5
|
+
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
|
6
|
+
word = camel_cased_word.to_s.gsub(/::/, '/')
|
7
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
8
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
9
|
+
word.tr!("-", "_")
|
10
|
+
word.downcase!
|
11
|
+
word
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ffmpeg-filter_graph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jon Sangster
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-reporters
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.1.8
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.1.8
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.13.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.13.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.4.4
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.4.4
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.7'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.7'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rdoc
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.12'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.12'
|
125
|
+
description: 'This gem generates complex filter-graphs for use with the ffmpeg application.
|
126
|
+
ie: Values for the -filter_complex command-line option.'
|
127
|
+
email:
|
128
|
+
- jon@ertt.ca
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".gitignore"
|
134
|
+
- ".travis.yml"
|
135
|
+
- CHANGELOG.md
|
136
|
+
- Gemfile
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- bin/console
|
140
|
+
- bin/setup
|
141
|
+
- ffmpeg-filter_graph.gemspec
|
142
|
+
- lib/ffmpeg/filter_graph.rb
|
143
|
+
- lib/ffmpeg/filter_graph/chain.rb
|
144
|
+
- lib/ffmpeg/filter_graph/filter.rb
|
145
|
+
- lib/ffmpeg/filter_graph/filter_factory.rb
|
146
|
+
- lib/ffmpeg/filter_graph/filters/audio.rb
|
147
|
+
- lib/ffmpeg/filter_graph/generate_filters_audio.rb
|
148
|
+
- lib/ffmpeg/filter_graph/generate_filters_audio_sink.rb
|
149
|
+
- lib/ffmpeg/filter_graph/generate_filters_audio_src.rb
|
150
|
+
- lib/ffmpeg/filter_graph/generate_filters_multimedia.rb
|
151
|
+
- lib/ffmpeg/filter_graph/generate_filters_video.rb
|
152
|
+
- lib/ffmpeg/filter_graph/generate_filters_video_src.rb
|
153
|
+
- lib/ffmpeg/filter_graph/graph.rb
|
154
|
+
- lib/ffmpeg/filter_graph/helper.rb
|
155
|
+
- lib/ffmpeg/filter_graph/utils/strings.rb
|
156
|
+
- lib/ffmpeg/filter_graph/version.rb
|
157
|
+
homepage: https://github.com/sangster/ffmpeg-filter_graph
|
158
|
+
licenses: []
|
159
|
+
metadata: {}
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
requirements: []
|
175
|
+
rubyforge_project:
|
176
|
+
rubygems_version: 2.5.1
|
177
|
+
signing_key:
|
178
|
+
specification_version: 4
|
179
|
+
summary: FFmpeg filter-graph generator
|
180
|
+
test_files: []
|
181
|
+
has_rdoc:
|