video_transcoding 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ebf08cb980d1fb3fbeea65f3785825cba62a2072
4
- data.tar.gz: b72309cbf62bf637f18651cb725c62226ec21c1d
3
+ metadata.gz: a80da681f701c9145241ab7427a34189885db02d
4
+ data.tar.gz: bb82f1a8edb1bb6ee6da02362146fa8cdbbf2b4f
5
5
  SHA512:
6
- metadata.gz: ee60f14f84e2776ca015a94b8baffb97e27d1c379fe30925bbc0ab4ccb765478db832ffa4966b406df3787e32f5cad084db54129701c152fda791c9a10221fc2
7
- data.tar.gz: cd49f7bbc29897be2afa3c927530af7685a65d9bf6aa27b25567e4b926591b5c56a886afd223a91e269604f8e7ccacbf5e7a461683a5922910e28b9d44458b3b
6
+ metadata.gz: 587f0b77d7804f4a65a5aa1aee7c802cd34b5ea25248b5d78ab02b9c44efbdbaca6db6ccec57b770a6a81070b3799100e599364d03bb83d7f538798388de3f66
7
+ data.tar.gz: dcdb174cfdd6fe0f379169ca569195efc6df074315c4cc3b39253caf92311e71016d0ea44e3cf46fe98fac16bb8da5d24b4bca7ea9256dc5a4c379b1480901b7
data/README.md CHANGED
@@ -62,7 +62,6 @@ Most of the tools in this package require other software to function properly, s
62
62
 
63
63
  * `HandBrakeCLI`
64
64
  * `ffmpeg`
65
- * `mkvmerge`
66
65
  * `mkvpropedit`
67
66
  * `mp4track`
68
67
  * `mplayer`
@@ -370,7 +369,7 @@ Which creates this Matroska file in the current working directory:
370
369
 
371
370
  If necessary, the `convert-video` tool may transcode audio tracks to AAC or Dolby Digital AC-3 format when converting to MP4 format.
372
371
 
373
- Chapter markers and metadata such as track titles are preserved. However, be aware that subtitle tracks are not converted.
372
+ Chapter markers, metadata such as track titles and most subtitles are converted. However, be aware that any Blu-ray Disc-format subtitles are ignored.
374
373
 
375
374
  ### Using `query-handbrake-log`
376
375
 
@@ -659,6 +658,25 @@ For a few problematic videos, I have to apply options like `--force-rate 23.976
659
658
 
660
659
  ## History
661
660
 
661
+ ### [0.15.0](https://github.com/donmelton/video_transcoding/releases/tag/0.15.0)
662
+
663
+ Sunday, January 15, 2017
664
+
665
+ * Modify `convert-video`, via [ #114](https://github.com/donmelton/video_transcoding/issues/114), to:
666
+ * Add support for text-based and DVD-style image-based subtitles. Please note that Blu-ray Disc-style image-based subtitles are _not_ supported due to MP4 format restrictions.
667
+ * Add a `--no-double` option which no longer assumes input files might contain two main audio tracks whose order needs to be swapped, or that a "missing" stereo AAC audio track needs to be added to MP4 output.
668
+ * Change the algoritm deciding when a "missing" stereo AAC audio track is added. Previously that only happened when the first track of the input MKV file was in surround format and there were no other audio tracks. Now it won't matter how many audio tracks are in the input.
669
+ * Use `ffmpeg` and `mkvpropedit` for conversion to MKV format instead of just `mkvmerge` which could not convert subtitle formats.
670
+ * Remove the dependency on `mkvmerge` and add a dependency on `mkvpropedit`.
671
+ * No longer pass the `-strict experimental` arguments to `ffmpeg` when using the built-in, native AAC encoder.
672
+ * Fix a bug preventing the detection of whether an audio track had the "default" flag set when parsing scan output from `HandBrakeCLI` versions 1.0.0 and later. This was caused by the integration of Libav version 12.0 in HandBrake on December 17, 2016.
673
+ * Fix a long-standing bug preventing the detection of all subtitles and disambiguation with chapter information in MP4 files when parsing scan output from `mp4track`.
674
+ * Remove support for the Freeware Advanced Audio Coder (FAAC) from the "FFmpeg" module since it's no longer included with `ffmpeg`.
675
+ * Remove the "mkvmerge.rb" source file and any references to the "MKVmerge" module since `convert-video` no longer needs it.
676
+ * Update the "README" document to:
677
+ * Remove `mkvmerge` from the "Requirements" section.
678
+ * Clarify subtitle support in the "Using `convert-video`" section.
679
+
662
680
  ### [0.14.0](https://github.com/donmelton/video_transcoding/releases/tag/0.14.0)
663
681
 
664
682
  Wednesday, January 4, 2017
data/bin/convert-video CHANGED
@@ -32,6 +32,8 @@ Usage: #{$PROGRAM_NAME} [OPTION]... [FILE]...
32
32
  (default: input filename with output format extension
33
33
  in current working directory)
34
34
  --use-m4v use `.m4v` extension instead of `.mp4` for MP4 output
35
+ --no-double don't swap order of first two audio tracks
36
+ or add stereo copy of main surround track
35
37
 
36
38
  -v, --verbose increase diagnostic information
37
39
  -q, --quiet decrease " "
@@ -39,7 +41,7 @@ Usage: #{$PROGRAM_NAME} [OPTION]... [FILE]...
39
41
  -h, --help display this help and exit
40
42
  --version output version information and exit
41
43
 
42
- Requires `HandBrakeCLI`, `mp4track`, `ffmpeg` and `mkvmerge`.
44
+ Requires `HandBrakeCLI`, `mp4track`, `ffmpeg` and `mkvpropedit`.
43
45
  HERE
44
46
  end
45
47
 
@@ -47,11 +49,13 @@ HERE
47
49
  super
48
50
  @output = nil
49
51
  @use_m4v = false
52
+ @double = true
50
53
  end
51
54
 
52
55
  def define_options(opts)
53
56
  opts.on('-o', '--output ARG') { |arg| @output = arg }
54
57
  opts.on('--use-m4v') { @use_m4v = true }
58
+ opts.on('--no-double') { @double = false }
55
59
  end
56
60
 
57
61
  def configure
@@ -62,7 +66,7 @@ HERE
62
66
  HandBrake.setup
63
67
  MP4track.setup
64
68
  FFmpeg.setup
65
- MKVmerge.setup
69
+ MKVpropedit.setup
66
70
  end
67
71
 
68
72
  def process_input(arg)
@@ -111,26 +115,18 @@ HERE
111
115
  track_order = media.info[:audio].keys
112
116
  stream = 0
113
117
 
114
- if media.info[:audio][1][:channels] > 2.0
115
- if track_order.size > 1
116
- if media.info[:audio][1][:language] == media.info[:audio][2][:language] and
117
- media.info[:audio][2][:format] == 'AAC' and
118
- media.info[:audio][2][:channels] <= 2.0
119
- first = track_order[0]
120
- track_order[0] = track_order[1]
121
- track_order[1] = first
122
- end
118
+ if @double and media.info[:audio][1][:channels] > 2.0
119
+ if track_order.size > 1 and
120
+ media.info[:audio][1][:language] == media.info[:audio][2][:language] and
121
+ media.info[:audio][2][:format] == 'AAC' and
122
+ media.info[:audio][2][:channels] <= 2.0
123
+ first = track_order[0]
124
+ track_order[0] = track_order[1]
125
+ track_order[1] = first
123
126
  else
124
127
  map_options.concat([
125
128
  '-map', "0:#{media.info[:audio][1][:stream]}"
126
129
  ])
127
-
128
- if FFmpeg.aac_encoder == 'aac'
129
- copy_options.concat([
130
- '-strict', 'experimental'
131
- ])
132
- end
133
-
134
130
  copy_options.concat([
135
131
  '-ac', '2',
136
132
  '-c:a:0', FFmpeg.aac_encoder,
@@ -150,17 +146,11 @@ HERE
150
146
  copy_options.concat([
151
147
  '-ac', '6',
152
148
  "-c:a:#{stream}", 'ac3',
153
- "-b:a:#{stream}", '384k'
149
+ "-b:a:#{stream}", '640k'
154
150
  ])
155
151
  elsif media.info[:audio][track][:channels] <= 2.0 and
156
152
  media.info[:audio][track][:format] != 'AAC' and
157
153
  media.info[:audio][track][:format] != 'AC3'
158
- if FFmpeg.aac_encoder == 'aac'
159
- copy_options.concat([
160
- '-strict', 'experimental'
161
- ])
162
- end
163
-
164
154
  copy_options.concat([
165
155
  '-ac', '2',
166
156
  "-c:a:#{stream}", FFmpeg.aac_encoder,
@@ -176,35 +166,49 @@ HERE
176
166
  end
177
167
  end
178
168
 
179
- ffmpeg_command = [
180
- FFmpeg.command_name,
181
- '-hide_banner',
182
- '-nostdin',
183
- '-i', media.path,
184
- *map_options,
185
- *copy_options,
186
- output
187
- ]
188
- Console.debug ffmpeg_command
189
- Console.info 'Converting with ffmpeg...'
169
+ forced_subtitle_track = 0
190
170
 
191
- begin
192
- IO.popen(ffmpeg_command, :err=>[:child, :out]) do |io|
193
- Signal.trap 'INT' do
194
- Process.kill 'INT', io.pid
171
+ unless media.info[:subtitle].empty?
172
+ track_order = media.info[:subtitle].keys
173
+ stream = 0
174
+
175
+ track_order.each do |track|
176
+ break unless media.info[:subtitle][track].has_key? :stream
177
+
178
+ subtitle_encoder = case media.info[:subtitle][track][:format]
179
+ when 'Text'
180
+ 'mov_text'
181
+ when 'Bitmap'
182
+ if media.info[:subtitle][track][:encoding] == 'VOBSUB'
183
+ 'copy'
184
+ else
185
+ nil
186
+ end
187
+ else
188
+ nil
195
189
  end
196
190
 
197
- io.each_char do |char|
198
- print char
191
+ unless subtitle_encoder.nil?
192
+ map_options.concat([
193
+ '-map', "0:#{media.info[:subtitle][track][:stream]}"
194
+ ])
195
+ copy_options.concat([
196
+ "-c:s:#{stream}", subtitle_encoder
197
+ ])
198
+ stream += 1
199
+
200
+ if forced_subtitle_track == 0 and media.info[:subtitle][track][:forced]
201
+ forced_subtitle_track = stream
202
+ end
199
203
  end
200
204
  end
201
- rescue SystemCallError => e
202
- raise "conversion failed: #{e}"
203
205
  end
204
206
 
205
- fail "conversion failed: #{media.path}" unless $CHILD_STATUS.exitstatus == 0
207
+ convert media, output, ['-movflags', 'disable_chpl', *map_options, *copy_options]
206
208
  mp4_media = Media.new(path: output, allow_directory: false)
207
- Console.debug media.info
209
+ Console.debug mp4_media.info
210
+
211
+ last_track = 0
208
212
 
209
213
  mp4_media.info[:audio].each do |track, info|
210
214
  if track == 1 and not info[:default]
@@ -215,61 +219,198 @@ HERE
215
219
  enabled = nil
216
220
  end
217
221
 
218
- unless enabled.nil?
219
- begin
220
- IO.popen([
221
- MP4track.command_name,
222
- '--track-index', track.to_s,
223
- '--enabled', enabled,
224
- output,
225
- ], :err=>[:child, :out]) do |io|
226
- io.each do |line|
227
- Console.debug line
228
- end
222
+ mp4_track_enable output, track, enabled
223
+ last_track += 1
224
+ end
225
+
226
+ mp4_media.info[:subtitle].each do |track, info|
227
+ if track == forced_subtitle_track and not info[:default]
228
+ enabled = 'true'
229
+ elsif track != forced_subtitle_track and info[:default]
230
+ enabled = 'false'
231
+ else
232
+ enabled = nil
233
+ end
234
+
235
+ mp4_track_enable output, last_track + track, enabled
236
+ end
237
+ end
238
+
239
+ def mp4_track_enable(output, track, enabled)
240
+ unless enabled.nil?
241
+ begin
242
+ IO.popen([
243
+ MP4track.command_name,
244
+ '--track-index', track.to_s,
245
+ '--enabled', enabled,
246
+ output,
247
+ ], :err=>[:child, :out]) do |io|
248
+ io.each do |line|
249
+ Console.debug line
229
250
  end
230
- rescue SystemCallError => e
231
- raise "adjusting audio enabled failed: #{e}"
232
251
  end
233
-
234
- fail "adjusting audio enabled failed: #{output}" unless $CHILD_STATUS.exitstatus == 0
252
+ rescue SystemCallError => e
253
+ raise "adjusting audio enabled failed: #{e}"
235
254
  end
255
+
256
+ fail "adjusting audio enabled failed: #{output}" unless $CHILD_STATUS.exitstatus == 0
236
257
  end
237
258
  end
238
259
 
239
260
  def convert_to_mkv(media, output)
240
- track_order = ['0:' + media.info[:stream].to_s]
241
- track_name_options = []
261
+ map_options = [
262
+ '-map', "0:#{media.info[:stream]}"
263
+ ]
264
+ copy_options = [
265
+ '-c:v', 'copy'
266
+ ]
267
+ audio_track_names = {}
268
+
269
+ unless media.info[:audio].empty?
270
+ track_order = media.info[:audio].keys
271
+ stream = 0
272
+
273
+ if @double and
274
+ track_order.size > 1 and
275
+ media.info[:audio][1][:language] == media.info[:audio][2][:language] and
276
+ media.info[:audio][1][:format] == 'AAC' and
277
+ media.info[:audio][1][:channels] <= 2.0 and
278
+ media.info[:audio][2][:channels] > 2.0
279
+ first = track_order[0]
280
+ track_order[0] = track_order[1]
281
+ track_order[1] = first
282
+ end
242
283
 
243
- media.info[:audio].each do |_, info|
244
- track_order << '0:' + info[:stream].to_s
245
- track_name_options.concat([
246
- '--track-name', "#{info[:stream]}:#{info[:name]}"
284
+ track_order.each do |track|
285
+ map_options.concat([
286
+ '-map', "0:#{media.info[:audio][track][:stream]}"
287
+ ])
288
+ copy_options.concat([
289
+ "-c:a:#{stream}", 'copy'
290
+ ])
291
+ stream += 1
292
+ audio_track_names[stream] = media.info[:audio][track][:name]
293
+ end
294
+ end
295
+
296
+ subtitle_track_names = {}
297
+ forced_subtitle_track = 0
298
+
299
+ unless media.info[:subtitle].empty?
300
+ track_order = media.info[:subtitle].keys
301
+ stream = 0
302
+
303
+ track_order.each do |track|
304
+ subtitle_encoder = case media.info[:subtitle][track][:format]
305
+ when 'Text'
306
+ 'text'
307
+ when 'Bitmap'
308
+ if media.info[:subtitle][track][:encoding] == 'VOBSUB'
309
+ 'dvdsub'
310
+ else
311
+ nil
312
+ end
313
+ else
314
+ nil
315
+ end
316
+
317
+ unless subtitle_encoder.nil?
318
+ map_options.concat([
319
+ '-map', "0:#{media.info[:subtitle][track][:stream]}"
320
+ ])
321
+ copy_options.concat([
322
+ "-c:s:#{stream}", subtitle_encoder
323
+ ])
324
+ stream += 1
325
+ subtitle_track_names[stream] = media.info[:subtitle][track][:name]
326
+
327
+ if forced_subtitle_track == 0 and media.info[:subtitle][track][:forced]
328
+ forced_subtitle_track = stream
329
+ end
330
+ end
331
+ end
332
+ end
333
+
334
+ convert media, output, [*map_options, *copy_options]
335
+ mkv_media = Media.new(path: output, allow_directory: false)
336
+ Console.debug mkv_media.info
337
+ mkvpropedit_options = [
338
+ '--tags', 'all:'
339
+ ]
340
+
341
+ mkv_media.info[:audio].each do |track, info|
342
+ unless audio_track_names[track].nil?
343
+ mkvpropedit_options.concat([
344
+ '--edit', "track:a#{track}",
345
+ '--set', "name=#{audio_track_names[track]}"
346
+ ])
347
+ end
348
+
349
+ if track == 1 and not info[:default]
350
+ default = 1
351
+ elsif track != 1 and info[:default]
352
+ default = 0
353
+ else
354
+ default = nil
355
+ end
356
+
357
+ unless default.nil?
358
+ mkvpropedit_options.concat([
359
+ '--edit', "track:a#{track}",
360
+ '--set', "flag-default=#{default}"
361
+ ])
362
+ end
363
+ end
364
+
365
+ mkv_media.info[:subtitle].each do |track, info|
366
+ unless subtitle_track_names[track].nil?
367
+ mkvpropedit_options.concat([
368
+ '--edit', "track:s#{track}",
369
+ '--set', "name=#{subtitle_track_names[track]}"
370
+ ])
371
+ end
372
+ end
373
+
374
+ if forced_subtitle_track != 0
375
+ mkvpropedit_options.concat([
376
+ '--edit', "track:s#{forced_subtitle_track}",
377
+ '--set', 'flag-forced=1'
247
378
  ])
248
379
  end
249
380
 
250
- if track_order.size > 2 and
251
- media.info[:audio][1][:language] == media.info[:audio][2][:language] and
252
- media.info[:audio][1][:format] == 'AAC' and
253
- media.info[:audio][1][:channels] <= 2.0 and
254
- media.info[:audio][2][:channels] > 2.0
255
- first = track_order[1]
256
- track_order[1] = track_order[2]
257
- track_order[2] = first
381
+ Console.info 'Adjusting properties and tags with mkvpropedit...'
382
+
383
+ begin
384
+ IO.popen([
385
+ MKVpropedit.command_name,
386
+ *mkvpropedit_options,
387
+ output,
388
+ ], :err=>[:child, :out]) do |io|
389
+ io.each do |line|
390
+ Console.debug line
391
+ end
392
+ end
393
+ rescue SystemCallError => e
394
+ raise "Adjusting properties and tags failed: #{e}"
258
395
  end
259
396
 
260
- mkvmerge_command = [
261
- MKVmerge.command_name,
262
- '--output', output,
263
- '--track-order', track_order.join(','),
264
- '--disable-track-statistics-tags'
397
+ fail "Adjusting properties and tags failed: #{output}" if $CHILD_STATUS.exitstatus == 2
398
+ end
399
+
400
+ def convert(media, output, ffmpeg_options)
401
+ ffmpeg_command = [
402
+ FFmpeg.command_name,
403
+ '-hide_banner',
404
+ '-nostdin',
405
+ '-i', media.path,
406
+ *ffmpeg_options,
407
+ output
265
408
  ]
266
- mkvmerge_command += track_name_options unless track_name_options.empty?
267
- mkvmerge_command << media.path
268
- Console.debug mkvmerge_command
269
- Console.info 'Converting with mkvmerge...'
409
+ Console.debug ffmpeg_command
410
+ Console.info 'Converting with ffmpeg...'
270
411
 
271
412
  begin
272
- IO.popen(mkvmerge_command, :err=>[:child, :out]) do |io|
413
+ IO.popen(ffmpeg_command, :err=>[:child, :out]) do |io|
273
414
  Signal.trap 'INT' do
274
415
  Process.kill 'INT', io.pid
275
416
  end
@@ -282,7 +423,7 @@ HERE
282
423
  raise "conversion failed: #{e}"
283
424
  end
284
425
 
285
- fail "conversion failed: #{media.path}" if $CHILD_STATUS.exitstatus == 2
426
+ fail "conversion failed: #{media.path}" unless $CHILD_STATUS.exitstatus == 0
286
427
  end
287
428
  end
288
429
  end
@@ -23,8 +23,6 @@ module VideoTranscoding
23
23
 
24
24
  if output =~ /--enable-libfdk-aac/
25
25
  properties[:aac_encoder] = 'libfdk_aac'
26
- elsif output =~ /--enable-libfaac/
27
- properties[:aac_encoder] = 'libfaac'
28
26
  else
29
27
  properties[:aac_encoder] = 'aac'
30
28
  end
@@ -120,11 +120,12 @@ module VideoTranscoding
120
120
  end
121
121
 
122
122
  subtitle.gsub(/\r/, '').each_line do |line|
123
- if line =~ /^ \+ ([0-9]+), .*\(iso639-2: ([a-z]{3})\) \((?:Text|Bitmap)\)\(([^)]+)\)/
123
+ if line =~ /^ \+ ([0-9]+), .*\(iso639-2: ([a-z]{3})\) \((Text|Bitmap)\)\(([^)]+)\)/
124
124
  track = $1.to_i
125
125
  track_info = {}
126
126
  track_info[:language] = $2
127
127
  track_info[:format] = $3
128
+ track_info[:encoding] = $4
128
129
  @info[:subtitle][track] = track_info
129
130
  end
130
131
  end
@@ -158,7 +159,7 @@ module VideoTranscoding
158
159
  track_info = @info[:audio][audio_track]
159
160
  track_info[:stream] = stream
160
161
 
161
- if attributes =~ /\(default\)/
162
+ if @scan =~ /[ ]+Stream #0[.:]#{stream}[^ ]*: Audio: [^\n]+(?:\n[^\n]+)?\(default\)/m
162
163
  track_info[:default] = true
163
164
  else
164
165
  track_info[:default] = false
@@ -257,7 +258,7 @@ module VideoTranscoding
257
258
  track_info[:default] = flags[index]
258
259
  track_info[:name] = names[index]
259
260
  end
260
- when 'text'
261
+ when '(sbtl)', '(subp)', 'text'
261
262
  subtitle_track += 1
262
263
 
263
264
  if @info[:subtitle].has_key? subtitle_track
@@ -270,6 +271,10 @@ module VideoTranscoding
270
271
 
271
272
  index += 1
272
273
  end
274
+
275
+ if subtitle_track > 0 and @scan =~ /[ ]+Chapter #0[.:]0: start /
276
+ @info[:subtitle].delete subtitle_track
277
+ end
273
278
  end
274
279
 
275
280
  @info
@@ -5,5 +5,5 @@
5
5
  #
6
6
 
7
7
  module VideoTranscoding
8
- VERSION = '0.14.0'
8
+ VERSION = '0.15.0'
9
9
  end
@@ -13,7 +13,6 @@ require 'video_transcoding/errors'
13
13
  require 'video_transcoding/ffmpeg'
14
14
  require 'video_transcoding/handbrake'
15
15
  require 'video_transcoding/media'
16
- require 'video_transcoding/mkvmerge'
17
16
  require 'video_transcoding/mkvpropedit'
18
17
  require 'video_transcoding/mp4track'
19
18
  require 'video_transcoding/mplayer'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: video_transcoding
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Don Melton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-05 00:00:00.000000000 Z
11
+ date: 2017-01-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Video Transcoding is a package of tools to transcode, inspect
@@ -39,7 +39,6 @@ files:
39
39
  - lib/video_transcoding/ffmpeg.rb
40
40
  - lib/video_transcoding/handbrake.rb
41
41
  - lib/video_transcoding/media.rb
42
- - lib/video_transcoding/mkvmerge.rb
43
42
  - lib/video_transcoding/mkvpropedit.rb
44
43
  - lib/video_transcoding/mp4track.rb
45
44
  - lib/video_transcoding/mplayer.rb
@@ -1,35 +0,0 @@
1
- #
2
- # mkvmerge.rb
3
- #
4
- # Copyright (c) 2013-2017 Don Melton
5
- #
6
-
7
- module VideoTranscoding
8
- module MKVmerge
9
- extend self
10
-
11
- COMMAND_NAME = 'mkvmerge'
12
-
13
- def setup
14
- Tool.provide(COMMAND_NAME, ['--version']) do |output, status, _|
15
- fail "#{COMMAND_NAME} failed during execution" unless status == 0
16
-
17
- unless output =~ /^mkvmerge v([0-9.]+)/
18
- Console.debug output
19
- fail "#{COMMAND_NAME} version unknown"
20
- end
21
-
22
- version = $1
23
- Console.info "#{$MATCH} found..."
24
-
25
- unless version =~ /^([0-9]+)\.([0-9]+)/ and (($1.to_i * 100) + $2.to_i) >= 700
26
- fail "#{COMMAND_NAME} version 7.0.0 or later required"
27
- end
28
- end
29
- end
30
-
31
- def command_name
32
- Tool.use(COMMAND_NAME)
33
- end
34
- end
35
- end