Module: Video2gif::FFmpeg

Includes:
Subtitles
Defined in:
lib/video2gif/ffmpeg.rb,
lib/video2gif/ffmpeg/subtitles.rb

Defined Under Namespace

Modules: Subtitles

Constant Summary collapse

CROP_REGEX =
/crop=([0-9]+\:[0-9]+\:[0-9]+\:[0-9]+)/

Constants included from Subtitles

Subtitles::KNOWN_BITMAP_FORMATS, Subtitles::KNOWN_TEXT_FORMATS

Class Method Summary collapse

Methods included from Subtitles

included

Class Method Details

.crop(options) ⇒ Object



46
47
48
49
50
51
52
53
54
55
# File 'lib/video2gif/ffmpeg.rb', line 46

def self.crop(options)
  crop_parameters = []

  crop_parameters << "w=#{options[:wregion]}" if options[:wregion]
  crop_parameters << "h=#{options[:hregion]}" if options[:hregion]
  crop_parameters << "x=#{options[:xoffset]}" if options[:xoffset]
  crop_parameters << "y=#{options[:yoffset]}" if options[:yoffset]

  'crop=' + crop_parameters.join(':') unless crop_parameters.empty?
end

.cropdetect_command(options, logger, executable: 'ffmpeg') ⇒ Object



210
211
212
213
214
215
216
217
218
219
# File 'lib/video2gif/ffmpeg.rb', line 210

def self.cropdetect_command(options, logger, executable: 'ffmpeg')
  command = ffmpeg_command(options, executable: executable)
  command << '-filter_complex' << "cropdetect=limit=#{options[:autocrop]}"
  command << '-f' << 'null'
  command << '-'

  logger.info(command.join(' ')) if options[:verbose] unless options[:quiet]

  command
end

.drawtext(options) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/video2gif/ffmpeg.rb', line 124

def self.drawtext(options)
  if options[:text]
    count_of_lines = options[:text].scan(/\\n/).count + 1

    drawtext_parameters = []
    drawtext_parameters << "x='#{ options[:xpos] || '(main_w/2-text_w/2)' }'"
    drawtext_parameters << "y='#{ options[:ypos] || "(main_h-line_h*1.5*#{count_of_lines})" }'"
    drawtext_parameters << "fontsize='#{ options[:textsize] || 32 }'"
    drawtext_parameters << "fontcolor='#{ options[:textcolor] || 'white' }'"
    drawtext_parameters << "borderw='#{ options[:textborder] || 2 }'"
    drawtext_parameters << "fontfile='#{ options[:textfont] || 'Arial'}'\\\\:style='#{options[:textvariant] || 'Bold' }'"
    drawtext_parameters << "text='#{text(options)}'"

    'drawtext=' + drawtext_parameters.join(':')
  end
end

.eq(options) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/video2gif/ffmpeg.rb', line 101

def self.eq(options)
  eq_parameters = []

  eq_parameters << "contrast=#{options[:contrast]}"     if options[:contrast]
  eq_parameters << "brightness=#{options[:brightness]}" if options[:brightness]
  eq_parameters << "saturation=#{options[:saturation]}" if options[:saturation]
  eq_parameters << "gamma=#{options[:gamma]}"           if options[:gamma]
  eq_parameters << "gamma_r=#{options[:gamma_r]}"       if options[:gamma_r]
  eq_parameters << "gamma_g=#{options[:gamma_g]}"       if options[:gamma_g]
  eq_parameters << "gamma_b=#{options[:gamma_b]}"       if options[:gamma_b]

  'eq=' + eq_parameters.join(":")
end

.ffmpeg_command(options, executable: 'ffmpeg') ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/video2gif/ffmpeg.rb', line 196

def self.ffmpeg_command(options, executable: 'ffmpeg')
  command = [executable]
  command << '-y'
  command << '-hide_banner'
  # command << '-analyzeduration' << '2147483647' << '-probesize' << '2147483647'
  command << '-loglevel' << 'verbose'
  command << '-ss' << options[:seek] if options[:seek]
  command << '-t' << options[:time] if options[:time]
  command << '-i' << options[:input_filename]
  command << '-an'
  command << '-sn'
  command << '-dn'
end

.ffprobe_command(options, logger, executable: 'ffprobe') ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
# File 'lib/video2gif/ffmpeg.rb', line 184

def self.ffprobe_command(options, logger, executable: 'ffprobe')
  command = [executable]
  command << '-v' << 'error'
  command << '-show_entries' << 'stream'
  command << '-print_format' << 'json'
  command << '-i' << options[:input_filename]

  logger.info(command.join(' ')) if options[:verbose] unless options[:quiet]

  command
end

.filtergraph(options) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/video2gif/ffmpeg.rb', line 164

def self.filtergraph(options)
  filtergraph = []

  filtergraph << bitmap_subtitles_scale_overlay(options)
  filtergraph << rate_with_interpolation(options)
  filtergraph << fps(options)
  filtergraph << options[:autocrop] if options[:autocrop]
  filtergraph << crop(options)
  filtergraph << zscale_and_tonemap(options)
  filtergraph << scale(options)
  filtergraph << eq(options)
  filtergraph << text_subtitles(options)
  filtergraph << drawtext(options)
  filtergraph << split
  filtergraph << palettegen(options)
  filtergraph << paletteuse(options)

  filtergraph.flatten.compact
end

.fps(options) ⇒ Object



42
43
44
# File 'lib/video2gif/ffmpeg.rb', line 42

def self.fps(options)
  "fps=#{ options[:fps] || 20 }"
end

.gif_command(options, logger, executable: 'ffmpeg') ⇒ Object



221
222
223
224
225
226
227
228
229
230
# File 'lib/video2gif/ffmpeg.rb', line 221

def self.gif_command(options, logger, executable: 'ffmpeg')
  command = ffmpeg_command(options, executable: executable)
  command << '-filter_complex' << filtergraph(options).join(',')
  command << '-f' << 'gif'
  command << options[:output_filename]

  logger.info(command.join(' ')) if options[:verbose] unless options[:quiet]

  command
end

.interpolate(options) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/video2gif/ffmpeg.rb', line 20

def self.interpolate(options)
  if options[:rate] && Float(options[:rate]) < 1  # only interpolate slowed down video
    minterpolate_parameters = []

    minterpolate_parameters << 'mi_mode=mci'
    minterpolate_parameters << 'mc_mode=aobmc'
    minterpolate_parameters << 'me_mode=bidir'
    minterpolate_parameters << 'me=epzs'
    minterpolate_parameters << 'vsbmc=1'
    minterpolate_parameters << "fps=#{video_info(options)[:avg_frame_rate] }/#{options[:rate]}"

    'minterpolate=' + minterpolate_parameters.join(':')
  end
end

.palettegen(options) ⇒ Object



145
146
147
148
149
150
151
152
# File 'lib/video2gif/ffmpeg.rb', line 145

def self.palettegen(options)
  palettegen_parameters = []

  palettegen_parameters << "#{ options[:palette] || 256 }"
  palettegen_parameters << "stats_mode=#{options[:palettemode] || 'diff'}"

  '[palettegen]palettegen=' + palettegen_parameters.join(':') + '[palette]'
end

.paletteuse(options) ⇒ Object



154
155
156
157
158
159
160
161
162
# File 'lib/video2gif/ffmpeg.rb', line 154

def self.paletteuse(options)
  paletteuse_parameters = []

  paletteuse_parameters << "dither=#{options[:dither] || 'floyd_steinberg'}"
  paletteuse_parameters << 'diff_mode=rectangle'
  paletteuse_parameters << "#{options[:palettemode] == 'single' ? 'new=1' : ''}"

  '[paletteuse][palette]paletteuse=' + paletteuse_parameters.join(':')
end

.rate(options) ⇒ Object



16
17
18
# File 'lib/video2gif/ffmpeg.rb', line 16

def self.rate(options)
  "setpts=PTS/#{options[:rate]}" if options[:rate]
end

.rate_with_interpolation(options) ⇒ Object



35
36
37
38
39
40
# File 'lib/video2gif/ffmpeg.rb', line 35

def self.rate_with_interpolation(options)
  [
    rate(options),
    interpolate(options)
  ]
end

.scale(options) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/video2gif/ffmpeg.rb', line 88

def self.scale(options)
  unless options[:tonemap]
    scale_parameters = []

    scale_parameters << 'flags=lanczos'
    scale_parameters << 'sws_dither=none'
    scale_parameters << "width=#{ options[:width] || 480 }"
    scale_parameters << "height=trunc(#{ options[:width] || 480 }/dar)"

    'scale=' + scale_parameters.join(':')
  end
end

.splitObject



141
142
143
# File 'lib/video2gif/ffmpeg.rb', line 141

def self.split
  'split[palettegen][paletteuse]'
end

.text(options) ⇒ Object



115
116
117
118
119
120
121
122
# File 'lib/video2gif/ffmpeg.rb', line 115

def self.text(options)
  options[:text].gsub(/\\n/,                                                        '')
                .gsub(/([:])/,                                                      '\\\\\\\\\\1')
                .gsub(/([,])/,                                                      '\\\\\\1')
                .gsub(/\b'\b/,                                                      "\u2019")
                .gsub(/\B"\b([^"\u201C\u201D\u201E\u201F\u2033\u2036\r\n]+)\b?"\B/, "\u201C\\1\u201D")
                .gsub(/\B'\b([^'\u2018\u2019\u201A\u201B\u2032\u2035\r\n]+)\b?'\B/, "\u2018\\1\u2019")
end

.tonemap(options) ⇒ Object



68
69
70
71
72
73
74
75
76
77
# File 'lib/video2gif/ffmpeg.rb', line 68

def self.tonemap(options)
  %W[
    zscale=transfer=linear:npl=100
    format=gbrpf32le
    zscale=primaries=bt709
    tonemap=tonemap=#{options[:tonemap]}:param=1.0:desat=0:peak=10
    zscale=transfer=bt709:matrix=bt709:range=tv
    format=yuv420p
  ]
end

.video_info(options) ⇒ Object



12
13
14
# File 'lib/video2gif/ffmpeg.rb', line 12

def self.video_info(options)
  options[:probe_infos][:streams].find { |s| s[:codec_type] == 'video' }
end

.zscale(options) ⇒ Object



57
58
59
60
61
62
63
64
65
66
# File 'lib/video2gif/ffmpeg.rb', line 57

def self.zscale(options)
  zscale_parameters = []

  zscale_parameters << 'dither=none'
  zscale_parameters << 'filter=lanczos'
  zscale_parameters << "width=#{ options[:width] || 480 }"
  zscale_parameters << "height=trunc(#{ options[:width] || 480 }/dar)"

  'zscale=' + zscale_parameters.join(':')
end

.zscale_and_tonemap(options) ⇒ Object



79
80
81
82
83
84
85
86
# File 'lib/video2gif/ffmpeg.rb', line 79

def self.zscale_and_tonemap(options)
  if options[:tonemap]
    [
      zscale(options),
      tonemap(options)
    ]
  end
end