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 +4 -4
- data/bin/mm_tool +0 -1
- data/lib/mm_tool/application_main.rb +20 -0
- data/lib/mm_tool/mm_movie.rb +19 -4
- data/lib/mm_tool/mm_movie_stream.rb +41 -6
- data/lib/mm_tool/mm_tool_cli.rb +33 -8
- data/lib/mm_tool/user_defaults.rb +16 -0
- data/lib/mm_tool/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92055cbeccfc0cd1628a7d89d281c1c179bace5eae9a099c62082e66a4d82744
|
4
|
+
data.tar.gz: 48327dccb7a80c5f1636a88bfc3406851eae42ece5e0c82eb747e664aab54880
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 767a31ed4096a56bfa3a481a7922692d7d42421f4ceec1f87e81dc178968a49f6ffba46f8caadf5b01b8cd244a5a85500942adafb61d0fcc18137a12605c5449
|
7
|
+
data.tar.gz: c518d44843912f60101507ede5ca4d4840850043375edc0be6b710465b6f6e5f2b9879c67fa62bff5702684a1dd69fb3784d10b78a5cfdb2a8b13b38072b99c2
|
data/bin/mm_tool
CHANGED
@@ -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
|
#=============================================================================
|
data/lib/mm_tool/mm_movie.rb
CHANGED
@@ -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,
|
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
|
-
|
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
|
-
|
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
|
data/lib/mm_tool/mm_tool_cli.rb
CHANGED
@@ -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
|
62
|
-
|
63
|
-
task
|
64
|
-
success
|
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
|
78
|
-
#
|
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
|
-
|
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
|
#----------------------------
|
data/lib/mm_tool/version.rb
CHANGED
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.
|
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:
|
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.
|
188
|
+
rubygems_version: 3.1.6
|
189
189
|
signing_key:
|
190
190
|
specification_version: 4
|
191
191
|
summary: Curate your movie files.
|