mm_tool 0.1.8 → 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 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.