mm_tool 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +4 -0
- data/.gitignore +24 -0
- data/Gemfile +3 -0
- data/LICENSE.md +22 -0
- data/README.md +15 -0
- data/Rakefile +2 -0
- data/Truth Tables.xlsx +0 -0
- data/bin/console +14 -0
- data/bin/mm_tool +19 -0
- data/bin/setup +8 -0
- data/lib/mm_tool.rb +21 -0
- data/lib/mm_tool/application_main.rb +172 -0
- data/lib/mm_tool/mm_movie.rb +217 -0
- data/lib/mm_tool/mm_movie_ignore_list.rb +68 -0
- data/lib/mm_tool/mm_movie_stream.rb +492 -0
- data/lib/mm_tool/mm_tool_cli.rb +322 -0
- data/lib/mm_tool/mm_user_defaults.rb +290 -0
- data/lib/mm_tool/output_helper.rb +121 -0
- data/lib/mm_tool/user_defaults.rb +377 -0
- data/lib/mm_tool/version.rb +3 -0
- data/mm_tool.gemspec +50 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: eb195a344b506ce9706580970d5e8db8843bf53b537161d63577dc84577089dc
|
4
|
+
data.tar.gz: b06f0eff5f6b0aeddd08b5ac1792d9cdf0869df5babab29fb771f9d3fac28e9d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a68290286f7db0bbcf5a0440c22ad3a213224439530c73280e70426fc52601a2d52d9c9dac278c7558de9b06317b8e284aae78ca6031b7b74f56f3b699c5823c
|
7
|
+
data.tar.gz: 769b79d23c56d70d9bbf4fa857629bad1376e332e7b6c87280a86d7d21dd0c54fe029c222a000dd7861c7aa764988372f81fc4e2dc754de3110bd79dd8a0bcb6
|
data/.gitattributes
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Rubymine noise
|
2
|
+
.idea/
|
3
|
+
|
4
|
+
# BBedit noise
|
5
|
+
*.bbprojectd
|
6
|
+
|
7
|
+
# Mac OS X noise
|
8
|
+
.DS_Store
|
9
|
+
|
10
|
+
# Ignore bundler lock files
|
11
|
+
Gemfile.lock
|
12
|
+
|
13
|
+
# Ignore pkg folder
|
14
|
+
/pkg
|
15
|
+
|
16
|
+
# Ignore build folders
|
17
|
+
build/*
|
18
|
+
|
19
|
+
# Yard cruft
|
20
|
+
/doc/
|
21
|
+
/.yardoc
|
22
|
+
|
23
|
+
# Aruba
|
24
|
+
/tmp/
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
===============
|
3
|
+
|
4
|
+
Copyright (c) 2020 Jim Derry
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/Truth Tables.xlsx
ADDED
Binary file
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "mm_tool"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/mm_tool
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#=============================================================================
|
4
|
+
# This module consolidates all of the classes the make up a complete MmTool
|
5
|
+
# application, and is spread throughout several files based mostly on the
|
6
|
+
# classes they contain.
|
7
|
+
#=============================================================================
|
8
|
+
module MmTool
|
9
|
+
require_relative '../lib/mm_tool'
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
#=============================================================================
|
14
|
+
# Main
|
15
|
+
#=============================================================================
|
16
|
+
|
17
|
+
cli = MmTool::MmToolCli.new(MmTool::ApplicationMain.shared_application)
|
18
|
+
cli.validate_prerequisites
|
19
|
+
cli.run(ARGV)
|
data/bin/setup
ADDED
data/lib/mm_tool.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Setup our load paths
|
2
|
+
libdir = File.expand_path(File.dirname(__FILE__))
|
3
|
+
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
4
|
+
|
5
|
+
require "mm_tool/output_helper"
|
6
|
+
require "mm_tool/user_defaults"
|
7
|
+
require "mm_tool/version"
|
8
|
+
|
9
|
+
require "mm_tool/application_main"
|
10
|
+
require "mm_tool/mm_tool_cli"
|
11
|
+
|
12
|
+
require 'mm_tool/mm_movie'
|
13
|
+
require 'mm_tool/mm_movie_stream'
|
14
|
+
|
15
|
+
require 'mm_tool/mm_movie_ignore_list'
|
16
|
+
require "mm_tool/mm_user_defaults"
|
17
|
+
|
18
|
+
module MmTool
|
19
|
+
class Error < StandardError
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module MmTool
|
2
|
+
|
3
|
+
#=============================================================================
|
4
|
+
# The main application.
|
5
|
+
#=============================================================================
|
6
|
+
class ApplicationMain
|
7
|
+
|
8
|
+
require 'mm_tool.rb'
|
9
|
+
|
10
|
+
#------------------------------------------------------------
|
11
|
+
# Attributes
|
12
|
+
#------------------------------------------------------------
|
13
|
+
attr_accessor :tempfile
|
14
|
+
|
15
|
+
#------------------------------------------------------------
|
16
|
+
# Initialize
|
17
|
+
#------------------------------------------------------------
|
18
|
+
def initialize
|
19
|
+
@tempfile = nil
|
20
|
+
@defaults = MmUserDefaults.shared_user_defaults
|
21
|
+
end
|
22
|
+
|
23
|
+
#------------------------------------------------------------
|
24
|
+
# Singleton accessor
|
25
|
+
#------------------------------------------------------------
|
26
|
+
def self.shared_application
|
27
|
+
unless @self
|
28
|
+
@self = self.new
|
29
|
+
end
|
30
|
+
@self
|
31
|
+
end
|
32
|
+
|
33
|
+
#------------------------------------------------------------
|
34
|
+
# Output a message to the screen, and if applicable, to
|
35
|
+
# the temporary file for later opening.
|
36
|
+
#------------------------------------------------------------
|
37
|
+
def output(message, command = false)
|
38
|
+
puts message
|
39
|
+
if @tempfile
|
40
|
+
if command
|
41
|
+
message = message + "\n"
|
42
|
+
else
|
43
|
+
message = message.lines.collect {|line| "# #{line}"}.join + "\n"
|
44
|
+
end
|
45
|
+
@tempfile&.write(message)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
#------------------------------------------------------------
|
50
|
+
# Return the transcode file header.
|
51
|
+
#------------------------------------------------------------
|
52
|
+
def transcode_script_header
|
53
|
+
<<~HEREDOC
|
54
|
+
#!/bin/sh
|
55
|
+
|
56
|
+
# Check this file, make any changes, and save it. It will be executed as soon
|
57
|
+
# as you close it. By default, this script will exit per the exit command
|
58
|
+
# below. Please further confirm that you wish to proceed with the proposed
|
59
|
+
# actions by commenting or removing the line below.
|
60
|
+
|
61
|
+
exit 1
|
62
|
+
|
63
|
+
HEREDOC
|
64
|
+
end
|
65
|
+
|
66
|
+
#------------------------------------------------------------
|
67
|
+
# Return the report header.
|
68
|
+
#------------------------------------------------------------
|
69
|
+
#noinspection RubyResolve
|
70
|
+
def information_header
|
71
|
+
info_table_src = TTY::Table.new
|
72
|
+
@defaults.label_value_pairs.each {|pair| info_table_src << pair}
|
73
|
+
info_table_src << ["Disposition Columns:", MmMovie.dispositions.join(', ')]
|
74
|
+
info_table_src << ["Transcode File Location:", self.tempfile ? tempfile&.path : 'n/a']
|
75
|
+
|
76
|
+
info_table = C.bold("Looking for file(s) and processing them with the following options:\n")
|
77
|
+
info_table << info_table_src.render(:basic) do |renderer|
|
78
|
+
a = @defaults.label_value_pairs.map {|p| p[0].length }.max + 1
|
79
|
+
b = OutputHelper.console_width - a - 2
|
80
|
+
renderer.alignments = [:right, :left]
|
81
|
+
renderer.multiline = true
|
82
|
+
renderer.column_widths = [a,b]
|
83
|
+
end << "\n\n"
|
84
|
+
end
|
85
|
+
|
86
|
+
#------------------------------------------------------------
|
87
|
+
# The main run loop, to be run for each file.
|
88
|
+
#------------------------------------------------------------
|
89
|
+
def run_loop(file_name)
|
90
|
+
|
91
|
+
if @defaults[:ignore_files]
|
92
|
+
MmMovieIgnoreList.shared_ignore_list.add(path: file_name)
|
93
|
+
output("Note: added #{file_name} to the list of files to be ignored.")
|
94
|
+
elsif @defaults[:unignore_files]
|
95
|
+
MmMovieIgnoreList.shared_ignore_list.remove(path: file_name)
|
96
|
+
output("Note: removed #{file_name} to the list of files to be ignored.")
|
97
|
+
else
|
98
|
+
@file_count[:processed] = @file_count[:processed] + 1
|
99
|
+
movie = MmMovie.new(with_file: file_name)
|
100
|
+
a = movie.interesting?
|
101
|
+
b = MmMovieIgnoreList.shared_ignore_list.include?(file_name)
|
102
|
+
c = movie.low_quality?
|
103
|
+
s = @defaults[:scan_type]&.to_sym
|
104
|
+
|
105
|
+
if (s == :normal && a && !b && !c) ||
|
106
|
+
(s == :all && !b) ||
|
107
|
+
(s == :flagged && b) ||
|
108
|
+
(s == :quality && c ) ||
|
109
|
+
(s == :force)
|
110
|
+
|
111
|
+
@file_count[:displayed] = @file_count[:displayed] + 1
|
112
|
+
output(file_name)
|
113
|
+
output(movie.format_table)
|
114
|
+
output(movie.stream_table)
|
115
|
+
output("#{movie.command_rename} ; \\", true)
|
116
|
+
output("#{movie.command_transcode} ; \\", true)
|
117
|
+
output(movie.command_review_post, true)
|
118
|
+
output("\n\n", true)
|
119
|
+
end
|
120
|
+
end # if
|
121
|
+
end
|
122
|
+
|
123
|
+
#------------------------------------------------------------
|
124
|
+
# Run the application with the given file/directory.
|
125
|
+
#------------------------------------------------------------
|
126
|
+
def run(file_or_dir)
|
127
|
+
|
128
|
+
@file_count = { :processed => 0, :displayed => 0 }
|
129
|
+
|
130
|
+
if @defaults[:transcode]
|
131
|
+
@tempfile = Tempfile.create(['mm_tool-', '.sh'])
|
132
|
+
@tempfile&.write(transcode_script_header)
|
133
|
+
# @tempfile.flush
|
134
|
+
end
|
135
|
+
|
136
|
+
if @defaults[:info_header]
|
137
|
+
output information_header
|
138
|
+
end
|
139
|
+
|
140
|
+
if File.file?(file_or_dir)
|
141
|
+
|
142
|
+
original_scan_type = @defaults[:scan_type]
|
143
|
+
@defaults[:scan_type] = :force
|
144
|
+
run_loop(file_or_dir)
|
145
|
+
@defaults[:scan_type] = original_scan_type
|
146
|
+
|
147
|
+
elsif File.directory?(file_or_dir)
|
148
|
+
|
149
|
+
extensions = @defaults[:container_files]&.join(',')
|
150
|
+
Dir.chdir(file_or_dir) do
|
151
|
+
Dir.glob("**/*.{#{extensions}}").map {|path| File.expand_path(path) }.sort.each do |file|
|
152
|
+
run_loop(file)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
else
|
157
|
+
output "Error: Execution should never have reached this point."
|
158
|
+
exit 1
|
159
|
+
end
|
160
|
+
|
161
|
+
output("#{File.basename($0)} processed #{@file_count[:processed]} files and displayed data for #{@file_count[:displayed]} of them.")
|
162
|
+
|
163
|
+
ensure
|
164
|
+
if @tempfile
|
165
|
+
@tempfile&.close
|
166
|
+
# @tempfile.unlink
|
167
|
+
end
|
168
|
+
end # run
|
169
|
+
|
170
|
+
end # class
|
171
|
+
|
172
|
+
end # module
|
@@ -0,0 +1,217 @@
|
|
1
|
+
module MmTool
|
2
|
+
|
3
|
+
#=============================================================================
|
4
|
+
# A movie as a self contained class. Instances of this class consist of
|
5
|
+
# one or more MmMovieStream instances, and contains intelligence about itself
|
6
|
+
# so that it can provide commands to ffmpeg and/or mkvpropedit as required.
|
7
|
+
# Upon creation it must be provided with a filename.
|
8
|
+
#=============================================================================
|
9
|
+
class MmMovie
|
10
|
+
|
11
|
+
require 'streamio-ffmpeg'
|
12
|
+
require 'tty-table'
|
13
|
+
require 'bytesize'
|
14
|
+
|
15
|
+
#------------------------------------------------------------
|
16
|
+
# This class method returns the known dispositions supported
|
17
|
+
# by ffmpeg. This array also reflects the output orders in
|
18
|
+
# the dispositions table field. Not combining them would
|
19
|
+
# result in a too-long table row.
|
20
|
+
#------------------------------------------------------------
|
21
|
+
def self.dispositions
|
22
|
+
%i(default dub original comment lyrics karaoke forced hearing_impaired visual_impaired clean_effects attached_pic timed_thumbnails)
|
23
|
+
end
|
24
|
+
|
25
|
+
#------------------------------------------------------------
|
26
|
+
# Initialize
|
27
|
+
#------------------------------------------------------------
|
28
|
+
def initialize(with_file:)
|
29
|
+
@defaults = MmUserDefaults.shared_user_defaults
|
30
|
+
@streams = MmMovieStream::streams(with_files: all_paths(with_file: with_file))
|
31
|
+
@format_metadata = FFMPEG::Movie.new(with_file).metadata[:format]
|
32
|
+
end
|
33
|
+
|
34
|
+
#------------------------------------------------------------
|
35
|
+
# Get the file-level 'duration' metadata.
|
36
|
+
#------------------------------------------------------------
|
37
|
+
def format_duration
|
38
|
+
seconds = @format_metadata[:duration]
|
39
|
+
seconds ? Time.at(seconds.to_i).utc.strftime("%H:%M:%S") : nil
|
40
|
+
end
|
41
|
+
|
42
|
+
#------------------------------------------------------------
|
43
|
+
# Get the file-level 'size' metadata.
|
44
|
+
#------------------------------------------------------------
|
45
|
+
def format_size
|
46
|
+
size = @format_metadata[:size]
|
47
|
+
size ? ByteSize.new(size) : 'unknown'
|
48
|
+
end
|
49
|
+
|
50
|
+
#------------------------------------------------------------
|
51
|
+
# Get the file-level 'title' metadata.
|
52
|
+
#------------------------------------------------------------
|
53
|
+
def format_title
|
54
|
+
@format_metadata&.dig(:tags, :title)
|
55
|
+
end
|
56
|
+
|
57
|
+
#------------------------------------------------------------
|
58
|
+
# Indicates whether any of the streams are of a lower
|
59
|
+
# quality than desired by the user.
|
60
|
+
#------------------------------------------------------------
|
61
|
+
def low_quality?
|
62
|
+
@streams.count {|stream| stream.low_quality?} > 0
|
63
|
+
end
|
64
|
+
|
65
|
+
#------------------------------------------------------------
|
66
|
+
# Indicates whether any of the streams are interesting.
|
67
|
+
#------------------------------------------------------------
|
68
|
+
def interesting?
|
69
|
+
@streams.count {|stream| stream.interesting?} > 0 || format_title
|
70
|
+
end
|
71
|
+
|
72
|
+
#------------------------------------------------------------
|
73
|
+
# Get the rendered text of the format_table.
|
74
|
+
#------------------------------------------------------------
|
75
|
+
def format_table
|
76
|
+
unless @format_table
|
77
|
+
@format_table = format_table_datasource.render(:basic) do |renderer|
|
78
|
+
renderer.column_widths = [10,10, 160]
|
79
|
+
renderer.multiline = true
|
80
|
+
renderer.padding = [0,1]
|
81
|
+
renderer.width = 1000
|
82
|
+
end
|
83
|
+
end
|
84
|
+
@format_table
|
85
|
+
end
|
86
|
+
|
87
|
+
#------------------------------------------------------------
|
88
|
+
# For the given table, get the rendered text of the table
|
89
|
+
# for output.
|
90
|
+
#------------------------------------------------------------
|
91
|
+
def stream_table
|
92
|
+
unless @stream_table
|
93
|
+
@stream_table = stream_table_datasource.render(:unicode) do |renderer|
|
94
|
+
renderer.alignments = [:center, :left, :left, :right, :right, :left, :left, :left, :left]
|
95
|
+
renderer.column_widths = [5,10,10,5,10,5,23,50,35]
|
96
|
+
renderer.multiline = true
|
97
|
+
renderer.padding = [0,1]
|
98
|
+
renderer.width = 1000
|
99
|
+
end # do
|
100
|
+
end
|
101
|
+
@stream_table
|
102
|
+
end
|
103
|
+
|
104
|
+
#------------------------------------------------------------
|
105
|
+
# The complete command to rename the main input file to
|
106
|
+
# include a tag indicating that it's the original.
|
107
|
+
#------------------------------------------------------------
|
108
|
+
def command_rename
|
109
|
+
src = @streams[0].source_file
|
110
|
+
dst = File.join(File.dirname(src), File.basename(src, '.*') + @defaults[:suffix] + File.extname(src))
|
111
|
+
"mv \"#{src}\" \"#{dst}\""
|
112
|
+
end
|
113
|
+
|
114
|
+
#------------------------------------------------------------
|
115
|
+
# The complete, proposed ffmpeg command to transcode the
|
116
|
+
# input file to an output file. It uses the 'new_input_path'
|
117
|
+
# as the input file.
|
118
|
+
#------------------------------------------------------------
|
119
|
+
def command_transcode
|
120
|
+
command = ["ffmpeg \\"]
|
121
|
+
@streams.each {|stream| command |= [" #{stream.instruction_input}"] if stream.instruction_input }
|
122
|
+
@streams.each {|stream| command << " #{stream.instruction_map}" if stream.instruction_map }
|
123
|
+
@streams.each {|stream| command << " #{stream.instruction_action}" if stream.instruction_action }
|
124
|
+
@streams.each {|stream| command << " #{stream.instruction_disposition}" if stream.instruction_disposition }
|
125
|
+
@streams.each {|stream| command << " #{stream.instruction_metadata}" if stream.instruction_metadata }
|
126
|
+
command << " -metadata title=\"#{format_title}\" \\" if format_title
|
127
|
+
command << " \"#{output_path}\""
|
128
|
+
command.join("\n")
|
129
|
+
end
|
130
|
+
|
131
|
+
#------------------------------------------------------------
|
132
|
+
# The complete command to view the output file after
|
133
|
+
# running the transcode command
|
134
|
+
#------------------------------------------------------------
|
135
|
+
def command_review_post
|
136
|
+
"\"#{$0}\" --no-use-external-subs \"#{output_path}\""
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
#============================================================
|
141
|
+
private
|
142
|
+
#============================================================
|
143
|
+
|
144
|
+
#------------------------------------------------------------
|
145
|
+
# Given the initial file, return an array of the initial
|
146
|
+
# file and associated SRTs, which are valid if they have
|
147
|
+
# no language, or a language specified in options.
|
148
|
+
#------------------------------------------------------------
|
149
|
+
def all_paths(with_file:)
|
150
|
+
all_paths = [with_file]
|
151
|
+
if @defaults[:use_external_subs]
|
152
|
+
base_path = File.join(File.dirname(with_file), File.basename(with_file, '.*'))
|
153
|
+
all_paths |= ([""] | @defaults[:keep_langs_subs]&.map {|lang| ".#{lang}" })
|
154
|
+
.select {|lang| File.file?("#{base_path}#{lang}.srt")}
|
155
|
+
.map {|lang| "#{base_path}#{lang}.srt"}
|
156
|
+
end
|
157
|
+
all_paths
|
158
|
+
end
|
159
|
+
|
160
|
+
#------------------------------------------------------------
|
161
|
+
# The name of the proposed output file, if different from
|
162
|
+
# the input file. This would be set in the event that the
|
163
|
+
# container of the input file is not one of the approved
|
164
|
+
# containers.
|
165
|
+
#------------------------------------------------------------
|
166
|
+
def output_path
|
167
|
+
path = @streams[0].source_file
|
168
|
+
if @defaults[:containers_preferred]&.include?(File.extname(path))
|
169
|
+
path
|
170
|
+
else
|
171
|
+
File.join(File.dirname(path), File.basename(path, '.*') + '.' + @defaults[:containers_preferred][0])
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
#------------------------------------------------------------
|
176
|
+
# Return a TTY::Table of the relevant format data.
|
177
|
+
#------------------------------------------------------------
|
178
|
+
def format_table_datasource
|
179
|
+
unless @format_table
|
180
|
+
@format_table = TTY::Table.new(header: %w(Duration: Size: Title:))
|
181
|
+
@format_table << [format_duration, format_size, format_title]
|
182
|
+
end
|
183
|
+
@format_table
|
184
|
+
end
|
185
|
+
|
186
|
+
#------------------------------------------------------------
|
187
|
+
# Return a TTY::Table of the movie, populated with the
|
188
|
+
# pertinent data of each stream.
|
189
|
+
#------------------------------------------------------------
|
190
|
+
def stream_table_datasource
|
191
|
+
unless @table
|
192
|
+
# Ensure that when we add the headers, they specifically are left-aligned.
|
193
|
+
headers = %w(index codec type w/# h/layout lang disposition title action(s))
|
194
|
+
.map { |header| {:value => header, :alignment => :left} }
|
195
|
+
|
196
|
+
@table = TTY::Table.new(header: headers)
|
197
|
+
|
198
|
+
@streams.each do |stream|
|
199
|
+
row = []
|
200
|
+
row << stream.input_specifier
|
201
|
+
row << stream.codec_name
|
202
|
+
row << stream.codec_type
|
203
|
+
row << stream.quality_01
|
204
|
+
row << stream.quality_02
|
205
|
+
row << stream.language
|
206
|
+
row << stream.dispositions
|
207
|
+
row << stream.title
|
208
|
+
row << stream.action_label
|
209
|
+
@table << row
|
210
|
+
end
|
211
|
+
end
|
212
|
+
@table
|
213
|
+
end # table
|
214
|
+
|
215
|
+
end # class
|
216
|
+
|
217
|
+
end # module
|