ffmpeg-filter_graph 0.1.3
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.
- 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:
|