ffmpeg-filter_graph 0.1.7 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|