mm_tool 0.1.8 → 0.1.9

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
  SHA256:
3
- metadata.gz: 58f5ac411fa199ac4b62febe8ca5896dfa7b490b4114038d8272681cee38b6d7
4
- data.tar.gz: bd6f59ce638b8e374ecc63213570a45ae84d7341e1796e3588887ca1ff948de1
3
+ metadata.gz: 92055cbeccfc0cd1628a7d89d281c1c179bace5eae9a099c62082e66a4d82744
4
+ data.tar.gz: 48327dccb7a80c5f1636a88bfc3406851eae42ece5e0c82eb747e664aab54880
5
5
  SHA512:
6
- metadata.gz: 7d32af155244f7019de5b7c9e9e37a89d91cc805463708f7f295efb7ac79b7982673d9d4e39bc18ef98aec00592642e8e1f9ec8e9588a51dd0df6f7bc53e2cbb
7
- data.tar.gz: db86d0614d20fc56ccf75542966535543cd46a33a90015bfd0644293659a2c77135013bbf44448a0108ef3ffd7f36d85451f4d24454f807893fb2b1ea7eae2a3
6
+ metadata.gz: 767a31ed4096a56bfa3a481a7922692d7d42421f4ceec1f87e81dc178968a49f6ffba46f8caadf5b01b8cd244a5a85500942adafb61d0fcc18137a12605c5449
7
+ data.tar.gz: c518d44843912f60101507ede5ca4d4840850043375edc0be6b710465b6f6e5f2b9879c67fa62bff5702684a1dd69fb3784d10b78a5cfdb2a8b13b38072b99c2
data/bin/mm_tool CHANGED
@@ -15,5 +15,4 @@ end
15
15
  #=============================================================================
16
16
 
17
17
  cli = MmTool::MmToolCli.new(MmTool::ApplicationMain.shared_application)
18
- cli.validate_prerequisites
19
18
  cli.run(ARGV)
@@ -1,5 +1,25 @@
1
1
  module MmTool
2
2
 
3
+ @@encoder_list = nil
4
+
5
+ #------------------------------------------------------------
6
+ # Module-level attribute provides list of installed encoders.
7
+ #------------------------------------------------------------
8
+ def self.encoder_list
9
+ unless @@encoder_list
10
+ @@encoder_list = %w()
11
+ codecs_allowed = %w(libx264 libx265 h264_qsv hevc_qsv h264_videotoolbox hevc_videotoolbox)
12
+ task = TTY::Command.new(printer: :null)
13
+
14
+ codecs_allowed.each do |codec|
15
+ result = task.run!("ffprobe -v quiet -codecs | grep #{codec}")
16
+ @@encoder_list << codec unless result.failure?
17
+ end
18
+ end
19
+ @@encoder_list
20
+ end
21
+
22
+
3
23
  #=============================================================================
4
24
  # The main application.
5
25
  #=============================================================================
@@ -27,7 +27,7 @@ module MmTool
27
27
  #------------------------------------------------------------
28
28
  def initialize(with_file:)
29
29
  @defaults = MmUserDefaults.shared_user_defaults
30
- @streams = MmMovieStream::streams(with_files: all_paths(with_file: with_file))
30
+ @streams = MmMovieStream::streams(with_files: all_paths(with_file: with_file), owner_ref: self)
31
31
  @format_metadata = FFMPEG::Movie.new(with_file).metadata[:format]
32
32
  end
33
33
 
@@ -47,6 +47,21 @@ module MmTool
47
47
  size ? ByteSize.new(size) : 'unknown'
48
48
  end
49
49
 
50
+ #------------------------------------------------------------
51
+ # Get the file-level 'bitrate' metadata.
52
+ #------------------------------------------------------------
53
+ def format_bitrate
54
+ size = @format_metadata[:bit_rate]
55
+ ByteSize.new(size).to_kb.to_i.to_s + "K"
56
+ end
57
+
58
+ #------------------------------------------------------------
59
+ # Get the file-level 'bitrate' metadata.
60
+ #------------------------------------------------------------
61
+ def raw_bitrate
62
+ @format_metadata[:bit_rate].to_i
63
+ end
64
+
50
65
  #------------------------------------------------------------
51
66
  # Get the file-level 'title' metadata.
52
67
  #------------------------------------------------------------
@@ -84,7 +99,7 @@ module MmTool
84
99
  def format_table
85
100
  unless @format_table
86
101
  @format_table = format_table_datasource.render(:basic) do |renderer|
87
- renderer.column_widths = [10,10, 160]
102
+ renderer.column_widths = [10,10,10,160]
88
103
  renderer.multiline = true
89
104
  renderer.padding = [0,1]
90
105
  renderer.width = 1000
@@ -192,8 +207,8 @@ module MmTool
192
207
  #------------------------------------------------------------
193
208
  def format_table_datasource
194
209
  unless @format_table
195
- @format_table = TTY::Table.new(header: %w(Duration: Size: Title:))
196
- @format_table << [format_duration, format_size, format_title]
210
+ @format_table = TTY::Table.new(header: %w(Duration: Size: Bitrate: Title:))
211
+ @format_table << [format_duration, format_size, format_bitrate, format_title]
197
212
  end
198
213
  @format_table
199
214
  end
@@ -15,7 +15,7 @@ module MmTool
15
15
  # an array of MmMovieStreams reflecting the streams present
16
16
  # in each of them.
17
17
  #------------------------------------------------------------
18
- def self.streams(with_files:)
18
+ def self.streams(with_files:, owner_ref:)
19
19
  # Arrays are passed around by reference; when this array is created and
20
20
  # used as a reference in each stream, and *also* returned from this class
21
21
  # method, everyone will still be using the same reference. It's important
@@ -24,7 +24,7 @@ module MmTool
24
24
  with_files.each_with_index do |path, i|
25
25
  ff_movie = FFMPEG::Movie.new(path)
26
26
  ff_movie.metadata[:streams].each do |stream|
27
- streams << MmMovieStream.new(stream_data: stream, source_file: path, file_number: i, streams_ref: streams)
27
+ streams << MmMovieStream.new(stream_data: stream, source_file: path, file_number: i, streams_ref: streams, owner_ref: owner_ref)
28
28
  end
29
29
  end
30
30
  streams
@@ -33,12 +33,13 @@ module MmTool
33
33
  #------------------------------------------------------------
34
34
  # Initialize
35
35
  #------------------------------------------------------------
36
- def initialize(stream_data:, source_file:, file_number:, streams_ref:)
36
+ def initialize(stream_data:, source_file:, file_number:, streams_ref:, owner_ref:)
37
37
  @defaults = MmUserDefaults.shared_user_defaults
38
38
  @data = stream_data
39
39
  @source_file = source_file
40
40
  @file_number = file_number
41
41
  @streams = streams_ref
42
+ @owner_ref = owner_ref
42
43
  end
43
44
 
44
45
  #------------------------------------------------------------
@@ -463,13 +464,47 @@ module MmTool
463
464
  # Given a codec, return the ffmpeg encoder string.
464
465
  #------------------------------------------------------------
465
466
  def encoder_string(for_codec:)
467
+
468
+ # If we have to use bitrate for controlling quality, we want to ensure that we don't
469
+ # exceed the current bitrate, which would be a lossy conversion to a larger file.
470
+ # This only applies to videotoolbox on Intel. We'll use constant quality for everything else.
471
+ # Pretty much, we don't want to use videotoolbox on macOS. Hey? Why can't we use qsv on macOS?
472
+ rate_h264 = (@owner_ref.raw_bitrate * 0.750).to_i
473
+ rate_h265 = (@owner_ref.raw_bitrate * 0.500).to_i
474
+
475
+ string_h264 = ByteSize.new(rate_h264).to_kb.to_i.to_s + "K"
476
+ string_h265 = ByteSize.new(rate_h265).to_kb.to_i.to_s + "K"
477
+
478
+ # Constant quality mode is only available for Apple Silicon!
479
+ # Can't use -q:v 65 without Apple Silicon.
480
+ # Have to use -b:v 6000K (for example) on Intel.
481
+ # HEVC doesn't seem to work on Intel without -pix_fmt yuv420p10le.
482
+ encoder_strings = {
483
+ :libx264 => "libx264 -crf 23 -force_key_frames chapters",
484
+ :libx265 => "libx265 -crf 28 -x265-params log-level=error -force_key_frames chapters",
485
+ :h264_qsv => "h264_qsv -global_quality 22 -lookahead 1 -force_key_frames chapters",
486
+ :hevc_qsv => "hevc_qsv -global_quality 22 -lookahead 1 -force_key_frames chapters",
487
+ :h264_videotoolbox => "h264_videotoolbox -b:v #{string_h264} -force_key_frames chapters",
488
+ :hevc_videotoolbox => "hevc_videotoolbox -b:v #{string_h265} -pix_fmt yuv420p10le -force_key_frames chapters"
489
+ }
490
+ if RUBY_PLATFORM.downcase.start_with?('arm64')
491
+ encoder_strings[:h264_videotoolbox] = "h264_videotoolbox -q:v 50 -force_key_frames chapters"
492
+ encoder_strings[:hevc_videotoolbox] = "hevc_videotoolbox -q:v 50 -force_key_frames chapters"
493
+ end
494
+
495
+ encoder = @defaults[:encoder].to_sym
496
+
466
497
  case for_codec.downcase
467
498
  when 'hevc'
468
- "libx265 -crf 28 -x265-params log-level=error -force_key_frames chapters"
499
+ return encoder_strings[:libx265] if [:auto, :libx265].include?(encoder)
500
+ return encoder_strings[:hevc_qsv] if encoder == :hevc_qsv
501
+ return encoder_strings[:hevc_videotoolbox] if encoder == :hevc_videotoolbox
469
502
  when 'h264'
470
- "libx264 -crf 23"
503
+ return encoder_strings[:libx264] if [:auto, :libx264].include?(encoder)
504
+ return encoder_strings[:h264_qsv] if encoder == :h264_qsv
505
+ return encoder_strings[:h264_videotoolbox] if encoder == :h264_videotoolbox
471
506
  when 'aac'
472
- "libfdk_aac"
507
+ return "libfdk_aac"
473
508
  else
474
509
  raise Exception.new "Error: somehow an unsupported codec '#{for_codec}' was specified."
475
510
  end
@@ -12,6 +12,11 @@ module MmTool
12
12
  # Initialize
13
13
  #------------------------------------------------------------
14
14
  def initialize(app_instance = MmTool::ApplicationMain.shared_application)
15
+ self.validate_prerequisites
16
+
17
+ decorated_list = MmTool.encoder_list.map{|s| C.bold(s)}.join(', ')
18
+ USER_DEFAULTS[:encoder][:help_desc].gsub!('ENCODER_LIST', decorated_list)
19
+
15
20
  @application = app_instance
16
21
  @defaults = MmUserDefaults.shared_user_defaults
17
22
  @defaults.register_defaults(with_hash: USER_DEFAULTS)
@@ -58,10 +63,10 @@ module MmTool
58
63
  #------------------------------------------------------------
59
64
  #noinspection RubyResolve
60
65
  def validate_prerequisites
61
- commands = %w(ffmpeg)
62
- codecs = %w(libx264 libx265 libfdk_aac)
63
- task = TTY::Command.new(printer: :null)
64
- success = true
66
+ commands = %w(ffmpeg ffprobe)
67
+ codecs_required = %w(libx264 libx265 libfdk_aac)
68
+ task = TTY::Command.new(printer: :null)
69
+ success = true
65
70
 
66
71
  # We'll check everything in items before failing, so that we can provide
67
72
  # a comprehensive list to the user. No one wants to see what's missing
@@ -74,17 +79,17 @@ module MmTool
74
79
  end
75
80
  exit 1 unless success
76
81
 
77
- # Now we'll make sure that all of the codecs that we're interested in
78
- # are installed as part of ffmpeg. This is necessary because not every
79
- # binary distribution supports non-free.
82
+ # Now we'll make sure that all of the required codecs are installed as part of ffmpeg.
83
+ # This is necessary because not every binary distribution supports non-free.
80
84
 
81
85
  # Again, we'll check them all before failing in order to list everything.
82
- codecs.each do |codec|
86
+ codecs_required.each do |codec|
83
87
  result = task.run!("ffprobe -v quiet -codecs | grep #{codec}")
84
88
  OutputHelper.print_error("Error: ffmpeg was built without support for the #{C.bold(codec)} codec, which is required.") if result.failure?
85
89
  success = success && result.success?
86
90
  end
87
91
  exit 1 unless success
92
+
88
93
  end
89
94
 
90
95
  #------------------------------------------------------------
@@ -271,6 +276,9 @@ module MmTool
271
276
  when '--fix-undefined-language'
272
277
  @defaults[:fix_undefined_language] = true
273
278
 
279
+ when '--encoder'
280
+ @defaults[:encoder] = validate_encoder_value(args[0], args[1])
281
+
274
282
  #-----------------------
275
283
  # Quality
276
284
  #-----------------------
@@ -328,6 +336,23 @@ module MmTool
328
336
  value
329
337
  end
330
338
 
339
+ #------------------------------------------------------------
340
+ # Perform a really simple validation of the given value for
341
+ # the given argument, returning the value if successful.
342
+ # ------------------------------------------------------------
343
+ #noinspection RubyResolve
344
+ def validate_encoder_value(arg, value)
345
+ if !value
346
+ OutputHelper.print_error_and_exit("Error: option #{C.bold(arg)} was specified, but no value was given.")
347
+ elsif value =~ /^-.*$/
348
+ OutputHelper.print_error_and_exit("Error: option #{C.bold(arg)} was specified, but the value #{C.bold(value)} looks like another option argument.")
349
+ end
350
+ unless MmTool.encoder_list.include?(value)
351
+ OutputHelper.print_error_and_exit("Error: Unknown encoder #{C.bold(value)} specified for option #{C.bold(arg)}. Use #{C.bold(File.basename($0) << '--help')} for supported encoders.")
352
+ end
353
+ value
354
+ end
355
+
331
356
  end # class
332
357
 
333
358
  end # module
@@ -356,6 +356,22 @@ module MmTool
356
356
  HEREDOC
357
357
  },
358
358
 
359
+ :encoder => {
360
+ :default => 'auto',
361
+ :value => nil,
362
+ :arg_short => nil,
363
+ :arg_long => '--encoder',
364
+ :arg_format => nil,
365
+ :item_label => 'Specify Non-default Encoder',
366
+ :help_group => 'Transcoding Options',
367
+ :help_desc => <<~HEREDOC
368
+ You can use #{C.bold('--encoder')} to specify the encoder to use when mm_tool determines that
369
+ transcoding should take place. Use one of ENCODER_LIST, or #{C.bold('auto')}. The default is
370
+ #{C.bold('%s')}. Note that #{C.bold('auto')} will choose #{C.bold('libx264')} or
371
+ #{C.bold('libx265')}, depending on the value of #{C.bold('--codecs-video-preferred')}.
372
+ HEREDOC
373
+ },
374
+
359
375
  #----------------------------
360
376
  # Quality Options
361
377
  #----------------------------
@@ -1,3 +1,3 @@
1
1
  module MmTool
2
- VERSION = "0.1.8"
2
+ VERSION = "0.1.9"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mm_tool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Derry
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-07 00:00:00.000000000 Z
11
+ date: 2024-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-table
@@ -185,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
185
  - !ruby/object:Gem::Version
186
186
  version: '0'
187
187
  requirements: []
188
- rubygems_version: 3.0.9
188
+ rubygems_version: 3.1.6
189
189
  signing_key:
190
190
  specification_version: 4
191
191
  summary: Curate your movie files.