ffmpeg-filter_graph 0.1.7 → 0.1.9
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/ffmpeg/filter_graph.rb +5 -2
- data/lib/ffmpeg/filter_graph/chain.rb +5 -5
- data/lib/ffmpeg/filter_graph/filter.rb +8 -1
- data/lib/ffmpeg/filter_graph/filter_factory.rb +3 -2
- data/lib/ffmpeg/filter_graph/generators/audio.rb +1 -1
- data/lib/ffmpeg/filter_graph/generators/video.rb +147 -4
- data/lib/ffmpeg/filter_graph/graph.rb +4 -9
- data/lib/ffmpeg/filter_graph/pad.rb +31 -0
- data/lib/ffmpeg/filter_graph/pads/inpad.rb +6 -0
- data/lib/ffmpeg/filter_graph/pads/outpad.rb +6 -0
- data/lib/ffmpeg/filter_graph/utils/helper.rb +34 -0
- data/lib/ffmpeg/filter_graph/utils/strings.rb +13 -11
- data/lib/ffmpeg/filter_graph/version.rb +2 -2
- metadata +34 -3
- data/lib/ffmpeg/filter_graph/helper.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a5c39c0cc22faf45b472abbeee75681b9614f11
|
4
|
+
data.tar.gz: 676a0ea923866dfeea1bdecf50a3d50eed40b537
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2684981e1bc3de441746ca716ef25f389acf6e8328bfef01a67b92863e8d78770190d60bf8e87797d2b81b27a87e29eed95124dd702c665a05a4f5ac181ac1c9
|
7
|
+
data.tar.gz: 34c61c836de845a2bd2c20da54c66402f60f7fe9e2d470bacd6f6a89b3daadd273576ef2466a3ebcbe6ec5c97a9d881ce44a083284e9ed0f1c01783ad5642db7
|
data/CHANGELOG.md
CHANGED
@@ -6,3 +6,9 @@
|
|
6
6
|
- Extracted from `rifftrax` gem.
|
7
7
|
- `FFmpeg::FilterGraph::Helper` can be merged into client classes for easy use.
|
8
8
|
- Filters can created with `editable` flag, to allow Timeline Editing options.
|
9
|
+
- FilterGraph: Option names that start with a digit can be prefixed with an
|
10
|
+
underscore: `:_0a` -> `0a`
|
11
|
+
- FilterGraph: Option names that include a dash can be specified in camelCase:
|
12
|
+
`:softKnee` -> `soft-knee`
|
13
|
+
- Moved `Helper` mixin to `FFmpeg::FilterGraph::Utils`
|
14
|
+
- Inpads/Outpads are encapsulated in classes, instead of naked strings
|
data/lib/ffmpeg/filter_graph.rb
CHANGED
@@ -3,15 +3,18 @@ module FFmpeg
|
|
3
3
|
end
|
4
4
|
end
|
5
5
|
|
6
|
+
require 'ffmpeg/filter_graph/utils/helper'
|
6
7
|
require 'ffmpeg/filter_graph/utils/strings'
|
7
8
|
|
8
|
-
require 'ffmpeg/filter_graph/helper'
|
9
|
-
|
10
9
|
require 'ffmpeg/filter_graph/filter'
|
11
10
|
require 'ffmpeg/filter_graph/filter_factory'
|
12
11
|
require 'ffmpeg/filter_graph/chain'
|
13
12
|
require 'ffmpeg/filter_graph/graph'
|
14
13
|
|
14
|
+
require 'ffmpeg/filter_graph/pad'
|
15
|
+
require 'ffmpeg/filter_graph/pads/inpad'
|
16
|
+
require 'ffmpeg/filter_graph/pads/outpad'
|
17
|
+
|
15
18
|
# These files are responsible for creating the Filter sub-classes
|
16
19
|
require 'ffmpeg/filter_graph/generators/audio'
|
17
20
|
require 'ffmpeg/filter_graph/generators/audio_sink'
|
@@ -3,19 +3,19 @@ module FFmpeg::FilterGraph
|
|
3
3
|
attr_accessor :inputs, :outputs, :filters
|
4
4
|
|
5
5
|
def initialize(inputs: [], outputs: [], filters: [])
|
6
|
-
self.inputs = inputs
|
7
|
-
self.outputs = outputs
|
6
|
+
self.inputs = Array(inputs).map { |pad| Pad.in(pad) }
|
7
|
+
self.outputs = Array(outputs).map { |pad| Pad.out(pad) }
|
8
8
|
self.filters = Array(filters).flatten.compact
|
9
9
|
end
|
10
10
|
|
11
11
|
def to_s
|
12
|
-
"#{join(inputs)} #{
|
12
|
+
"#{join(inputs)} #{join(filters, ', ')} #{join(outputs)}".strip
|
13
13
|
end
|
14
14
|
|
15
15
|
private
|
16
16
|
|
17
|
-
def join(arr)
|
18
|
-
arr.map
|
17
|
+
def join(arr, sep = '')
|
18
|
+
arr.map(&:to_s).join(sep)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module FFmpeg::FilterGraph
|
2
2
|
class Filter
|
3
|
+
include Utils::Strings
|
4
|
+
|
3
5
|
class << self
|
4
6
|
# @param name [String] Sets the filter-name output by this filter
|
5
7
|
def name(name = nil)
|
@@ -62,8 +64,13 @@ module FFmpeg::FilterGraph
|
|
62
64
|
# "k1=v1:k2=v2:k3=v3". Will return nil of there are no set values
|
63
65
|
def join_options(*keys)
|
64
66
|
opts = options
|
65
|
-
parts = keys.map { |k| "#{k}=#{opts[k]}" if opts[k] }.compact
|
67
|
+
parts = keys.map { |k| "#{translate_key(k)}=#{opts[k]}" if opts[k] }.compact
|
66
68
|
parts.join(':') if parts.any?
|
67
69
|
end
|
70
|
+
|
71
|
+
# Remove leading underscores and convert to snake case
|
72
|
+
def translate_key(key)
|
73
|
+
underscore(key.to_s.gsub(/_*(.+)/, '\1'))
|
74
|
+
end
|
68
75
|
end
|
69
76
|
end
|
@@ -26,7 +26,8 @@ module FFmpeg::FilterGraph
|
|
26
26
|
# default method of constructing the string of options in the filter's
|
27
27
|
# output.
|
28
28
|
# @see Filter#options_string
|
29
|
-
def initialize(class_name, required, optional, editable,
|
29
|
+
def initialize(class_name, required = nil, optional = nil, editable = false,
|
30
|
+
&options_string)
|
30
31
|
self.class_name = class_name.to_s
|
31
32
|
self.required = required || []
|
32
33
|
self.optional = optional || []
|
@@ -38,7 +39,7 @@ module FFmpeg::FilterGraph
|
|
38
39
|
# @param helper_module [Module] an optional module to create a helper-method
|
39
40
|
# in. ex: if the filter class is named MyFilter, a method will be created
|
40
41
|
# in the form of Helper.my_filter(*args); MyFilter.new(*args) end
|
41
|
-
def create_class_in(mod, helper_module: Helper)
|
42
|
+
def create_class_in(mod, helper_module: Utils::Helper)
|
42
43
|
# We need to make these local vars, to work in the Class.new block
|
43
44
|
cn = class_name.to_s
|
44
45
|
|
@@ -10,7 +10,7 @@ module FFmpeg::FilterGraph
|
|
10
10
|
},
|
11
11
|
|
12
12
|
AtaDenoise: {
|
13
|
-
optional: [:
|
13
|
+
optional: [:_0a, :_0b, :_1a, :_1b, :_2a, :_2b, :s]
|
14
14
|
},
|
15
15
|
|
16
16
|
Bbox: {
|
@@ -84,9 +84,9 @@ module FFmpeg::FilterGraph
|
|
84
84
|
},
|
85
85
|
|
86
86
|
Convolution: {
|
87
|
-
optional: [:
|
88
|
-
:
|
89
|
-
:
|
87
|
+
optional: [:_0m, :_1m, :_2m, :_3m,
|
88
|
+
:_0rdiv, :_1rdiv, :_2rdiv, :_3rdiv,
|
89
|
+
:_0bias, :_1bias, :_2bias, :_3bias]
|
90
90
|
},
|
91
91
|
|
92
92
|
Copy: {},
|
@@ -111,6 +111,149 @@ module FFmpeg::FilterGraph
|
|
111
111
|
DataScope: {
|
112
112
|
required: [:size, :x, :y, :mode, :axis]
|
113
113
|
},
|
114
|
+
|
115
|
+
DctDnoiz: {
|
116
|
+
optional: [:sigma, :overlap, :expr, :n]
|
117
|
+
},
|
118
|
+
|
119
|
+
Deband: {
|
120
|
+
required: [:direction],
|
121
|
+
optional: [:_1thr, :_2thr, :_3thr, :_4thr, :range, :blur]
|
122
|
+
},
|
123
|
+
|
124
|
+
Decimate: {
|
125
|
+
optional: [:cycle, :dupthresh, :scthresh, :blockx, :blocky, :ppsrc,
|
126
|
+
:chroma]
|
127
|
+
},
|
128
|
+
|
129
|
+
Deflate: {
|
130
|
+
optional: [:threshold0, :threshold1, :threshold2, :threshold3]
|
131
|
+
},
|
132
|
+
|
133
|
+
Dejudder: {
|
134
|
+
optional: [:cycle]
|
135
|
+
},
|
136
|
+
|
137
|
+
Delogo: {
|
138
|
+
required: [:x, :y, :w, :h],
|
139
|
+
optional: [:band, :show]
|
140
|
+
},
|
141
|
+
|
142
|
+
Deshake: {
|
143
|
+
optional: [:x, :y, :w, :h, :rx, :ry, :edge, :blocksize, :contrast,
|
144
|
+
:search, :filename, :opencl]
|
145
|
+
},
|
146
|
+
|
147
|
+
Detelecine: {
|
148
|
+
optional: [:first_field, :pattern, :start_frame]
|
149
|
+
},
|
150
|
+
|
151
|
+
Dilation: {
|
152
|
+
optional: [:threshold0, :threshold1, :threshold2, :threshold3,
|
153
|
+
:coordinates]
|
154
|
+
},
|
155
|
+
|
156
|
+
Displace: {
|
157
|
+
optional: [:edge]
|
158
|
+
},
|
159
|
+
|
160
|
+
DrawBox: {
|
161
|
+
optional: [:x, :y, :width, :height, :thickness, :color]
|
162
|
+
},
|
163
|
+
|
164
|
+
DrawGraph: {
|
165
|
+
required: [:m1, :fg1, :m2, :fg2, :m3, :fg3, :m4, :fg4, :min, :max, :bg],
|
166
|
+
optional: [:mode, :slide, :size]
|
167
|
+
},
|
168
|
+
|
169
|
+
ADrawGraph: {
|
170
|
+
required: [:m1, :fg1, :m2, :fg2, :m3, :fg3, :m4, :fg4, :min, :max, :bg],
|
171
|
+
optional: [:mode, :slide, :size]
|
172
|
+
},
|
173
|
+
|
174
|
+
DrawGrid: {
|
175
|
+
required: [:color],
|
176
|
+
optional: [:x, :y, :width, :height, :thickness]
|
177
|
+
},
|
178
|
+
|
179
|
+
DrawText: {
|
180
|
+
optional: [:box, :boxborderw, :boxcolor, :borderw, :bordercolor,
|
181
|
+
:expansion, :fix_bounds, :fontcolor, :fontcolor_expr, :font,
|
182
|
+
:fontfile, :alpha, :fontsize, :text_shaping, :ft_load_flags,
|
183
|
+
:shadowcolor, :shadowx, :shadowy, :start_number, :tabsize,
|
184
|
+
:timecode, :timecode_rate, :text, :textfile, :reload, :x, :y]
|
185
|
+
},
|
186
|
+
|
187
|
+
EdgeDetect: {
|
188
|
+
optional: [:low, :high, :mode],
|
189
|
+
},
|
190
|
+
|
191
|
+
Eq: {
|
192
|
+
optional: [:contrast, :brightness, :saturation, :gamma, :gamma_r,
|
193
|
+
:gamma_g, :gamma_b, :gamma_weight, :eval]
|
194
|
+
},
|
195
|
+
|
196
|
+
ExtractPlanes: {
|
197
|
+
required: [:planes]
|
198
|
+
},
|
199
|
+
|
200
|
+
Elbg: {
|
201
|
+
required: [:pal8],
|
202
|
+
optional: [:codebook_length, :nb_steps, :seed]
|
203
|
+
},
|
204
|
+
|
205
|
+
Fade: {
|
206
|
+
optional: [:type, :start_frame, :nb_frames, :alpha, :start_time,
|
207
|
+
:duration, :color]
|
208
|
+
},
|
209
|
+
|
210
|
+
# TODO fftfilt (has all-caps options)
|
211
|
+
|
212
|
+
Field: {
|
213
|
+
required: [:type]
|
214
|
+
},
|
215
|
+
|
216
|
+
FieldHint: {
|
217
|
+
required: [:hint],
|
218
|
+
optional: [:mode]
|
219
|
+
},
|
220
|
+
|
221
|
+
FieldMatch: {
|
222
|
+
optional: [:order, :mode, :ppsrc, :field, :mchroma, :y0, :y1, :scthresh,
|
223
|
+
:combmatch, :combdbg, :cthresh, :chroma, :blockx, :blocky,
|
224
|
+
:combpel]
|
225
|
+
},
|
226
|
+
|
227
|
+
FieldOrder: {
|
228
|
+
optional: [:order]
|
229
|
+
},
|
230
|
+
|
231
|
+
Fifo: {},
|
232
|
+
|
233
|
+
AFifo: {},
|
234
|
+
|
235
|
+
# TODO find_rect
|
236
|
+
# TODO cover_rect
|
237
|
+
|
238
|
+
Format: {
|
239
|
+
required: [:pix_fmts],
|
240
|
+
},
|
241
|
+
|
242
|
+
Fps: {
|
243
|
+
optional: [:fps, :round, :start_time]
|
244
|
+
},
|
245
|
+
|
246
|
+
FramePack: {
|
247
|
+
optional: [:format]
|
248
|
+
},
|
249
|
+
|
250
|
+
FrameRate: {
|
251
|
+
optional: [:fps, :interp_start, :interp_end, :scene, :flag]
|
252
|
+
},
|
253
|
+
|
254
|
+
FrameStep: {
|
255
|
+
optional: [:step]
|
256
|
+
},
|
114
257
|
}.each do |class_name, opts|
|
115
258
|
FilterFactory.create(class_name, opts).create_class_in(self)
|
116
259
|
end
|
@@ -3,20 +3,15 @@ module FFmpeg::FilterGraph
|
|
3
3
|
attr_accessor :chains, :outputs
|
4
4
|
|
5
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))
|
6
|
+
self.chains = Array(chains)
|
7
|
+
self.outputs = Array(outputs).map { |pad| Pad.out(pad) }
|
12
8
|
end
|
13
9
|
|
14
10
|
def to_s
|
15
11
|
[
|
16
12
|
chains.map(&:to_s).join('; '),
|
17
|
-
outputs.map
|
18
|
-
].join('')
|
13
|
+
outputs.map(&:to_s).join(''),
|
14
|
+
].join(' ').strip
|
19
15
|
end
|
20
16
|
end
|
21
17
|
end
|
22
|
-
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module FFmpeg::FilterGraph
|
2
|
+
class Pad
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def self.in(pad)
|
6
|
+
case pad
|
7
|
+
when Pads::Outpad then fail ArgumentError, 'cannot use Outpad as input'
|
8
|
+
when Pad then pad
|
9
|
+
else Pads::Inpad.new(pad.to_s)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.out(pad)
|
14
|
+
case pad
|
15
|
+
when Pads::Inpad then fail ArgumentError, 'cannot use Inpad as output'
|
16
|
+
when Pad then pad
|
17
|
+
else Pads::Outpad.new(pad.to_s)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(name)
|
22
|
+
fail ArgumentError, 'name cannot be empty' if name.nil? || name.empty?
|
23
|
+
|
24
|
+
@name = name
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"[#{name}]"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module FFmpeg::FilterGraph
|
2
|
+
module Utils
|
3
|
+
module Helper
|
4
|
+
CHANNELS = %w(FL FR FC LFE RL RR)
|
5
|
+
|
6
|
+
def graph(*args); Graph.new(*args) end
|
7
|
+
def chain(*args); Chain.new(*args) end
|
8
|
+
|
9
|
+
# Filter-specific helpers will be added to this module by default, by the
|
10
|
+
# FilterFactory class.
|
11
|
+
|
12
|
+
def surround_channels(prefix = '', *channels)
|
13
|
+
channels = CHANNELS if channels.empty?
|
14
|
+
channels.map(&:to_s).map(&:upcase).map { |c| "#{prefix}#{c}" }
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return the number of channels in the given channel layout
|
18
|
+
def count_channels(ch)
|
19
|
+
case ch
|
20
|
+
when Fixnum then ch
|
21
|
+
when '7.1' then 8
|
22
|
+
when '6.1' then 7
|
23
|
+
when '5.1' then 6
|
24
|
+
when '4.1' then 5
|
25
|
+
when '3.1' then 4
|
26
|
+
when '2.1' then 3
|
27
|
+
when 'stereo' then 2
|
28
|
+
when 'mono' then 1
|
29
|
+
else fail 'unknown layout'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,14 +1,16 @@
|
|
1
|
-
module FFmpeg::FilterGraph
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
module FFmpeg::FilterGraph
|
2
|
+
module Utils
|
3
|
+
module Strings
|
4
|
+
# From github: activesupport/lib/active_support/inflector/methods.rb
|
5
|
+
def underscore(camel_cased_word)
|
6
|
+
return camel_cased_word unless camel_cased_word =~ /[A-Z-]/
|
7
|
+
word = camel_cased_word.dup
|
8
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
9
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
10
|
+
word.tr!('-', '_')
|
11
|
+
word.downcase!
|
12
|
+
word
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffmpeg-filter_graph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Sangster
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 8.2.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 8.2.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.11.2
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.11.2
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: minitest
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,7 +173,10 @@ files:
|
|
145
173
|
- lib/ffmpeg/filter_graph/generators/video_sink.rb
|
146
174
|
- lib/ffmpeg/filter_graph/generators/video_src.rb
|
147
175
|
- lib/ffmpeg/filter_graph/graph.rb
|
148
|
-
- lib/ffmpeg/filter_graph/
|
176
|
+
- lib/ffmpeg/filter_graph/pad.rb
|
177
|
+
- lib/ffmpeg/filter_graph/pads/inpad.rb
|
178
|
+
- lib/ffmpeg/filter_graph/pads/outpad.rb
|
179
|
+
- lib/ffmpeg/filter_graph/utils/helper.rb
|
149
180
|
- lib/ffmpeg/filter_graph/utils/strings.rb
|
150
181
|
- lib/ffmpeg/filter_graph/version.rb
|
151
182
|
homepage: https://github.com/sangster/ffmpeg-filter_graph
|
@@ -1,32 +0,0 @@
|
|
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
|