transcode 1.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0375af3ce6a6249106ace1b6bf411e0b7bc268463b290162e8db0a4c2e3a4daf
4
+ data.tar.gz: fa721d63eea507a241bdc1a37ca096dffb3077d0c69d06610a1cff7a03284c0e
5
+ SHA512:
6
+ metadata.gz: 7a5453ee0956ab79fba302cbdccde7029a26d09a5327039dfd65c50ab4f62a9c0f496e09618ae3688b2eb582bcfbe4b22be988dff3f40c9cbee060a58a9319d7
7
+ data.tar.gz: 11bb4241bd5f2e39afa15f3581308e7ec10f0a34b43e945f3bd65cdf79c76f21e3360efe0dad33c887ef905ba27144eb8af9190d5334eedd9f23e9ed0683ad0a
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2020, David Rabkin
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,52 @@
1
+ # Transcode
2
+
3
+ Tool to transcode batch of video files.
4
+
5
+ * [About](#about)
6
+ * [Installation](#installation)
7
+ * [Usage](#usage)
8
+ * [License](#license)
9
+
10
+ ## About
11
+
12
+ Hi, I'm [David Rabkin](https://www.rabkin.co.il). I created this tool to
13
+ enhance [Don Melton](http://donmelton.com/)'s [Video
14
+ Transcoding](https://github.com/donmelton/video_transcoding/).
15
+
16
+ ## Installation
17
+
18
+ The tool is designed to work on macOS, GNU/Linux, Windows, Unix-like OS. It is
19
+ packaged as a Gem and require Ruby version 2.3 or later. See «[Installing
20
+ Ruby](https://www.ruby-lang.org/en/documentation/installation/)» if you don't
21
+ have the proper version on your platform.
22
+
23
+ Use this command to install:
24
+
25
+ gem install transcode
26
+
27
+ ### Updating
28
+
29
+ Use this command to update the package:
30
+
31
+ gem update transcode
32
+
33
+ ### Requirements
34
+
35
+ See [Video Transcoding requirements](https://github.com/donmelton/video_transcoding/blob/master/README.md?ts=2#requirements).
36
+
37
+ ## Usage
38
+ Usage: transcode [options].
39
+ -a, --act Real encoding.
40
+ -s, --sca Scans files at the directory.
41
+ -m, --mp3 Converts files to mp3.
42
+ -d, --dir dir Directory to transcode.
43
+ -i, --tit tit Specific title by number.
44
+ -o, --out out Directory to output.
45
+ -u, --aud aud Audio stream numbers.
46
+ -t, --sub sub Subtitle stream numbers.
47
+ -w, --wid wid Width of the table.
48
+
49
+ ## License
50
+
51
+ Transcode is copyright [David Rabkin](http://www.rabkin.co.il/) and
52
+ available under a [2-Claus BSD license](https://github.com/rdavid/transcode/blob/master/LICENSE).
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # vi:ts=2 sw=2 tw=79 et lbr wrap
5
+ # Copyright 2018-present David Rabkin
6
+
7
+ require 'pidfile'
8
+ require_relative '../lib/transcode'
9
+
10
+ PidFile.new
11
+ Transcode::Transcoder.new.do
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vi:ts=2 sw=2 tw=79 et lbr wrap
4
+ # Copyright 2018-present David Rabkin
5
+
6
+ require_relative 'transcode/transcoder'
7
+ require_relative 'transcode/version'
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vi:ts=2 sw=2 tw=79 et lbr wrap
4
+ # Copyright 2018-present David Rabkin
5
+
6
+ require 'optparse'
7
+
8
+ module Transcode
9
+ # Handles input parameters.
10
+ class Configuration # rubocop:disable Metrics/ClassLength
11
+ attr_reader :files
12
+ DIC = [
13
+ ['-a', '--act', 'Real encoding.', nil, :act],
14
+ ['-s', '--sca', 'Scans files at the directory.', nil, :sca],
15
+ ['-m', '--mp3', 'Converts files to mp3.', nil, :mp3],
16
+ ['-d', '--dir dir', 'Directory to transcode.', String, :dir],
17
+ ['-i', '--tit tit', 'Specific title by number.', Array, :tit],
18
+ ['-o', '--out out', 'Directory to output.', String, :out],
19
+ ['-u', '--aud aud', 'Audio stream numbers.', Array, :aud],
20
+ ['-t', '--sub sub', 'Subtitle stream numbers.', Array, :sub],
21
+ ['-w', '--wid wid', 'Width of the table.', Integer, :wid]
22
+ ].freeze
23
+ EXT = %i[
24
+ avi flv m4v mkv mp4 mpg mpeg ts webm vob wmv
25
+ ].map(&:to_s).join(',').freeze
26
+
27
+ def initialize
28
+ @options = {}
29
+ OptionParser.new do |o|
30
+ o.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options]."
31
+ DIC.each { |f, p, d, t, k| o.on(f, p, t, d) { |i| @options[k] = i } }
32
+ end.parse!
33
+ find_dir
34
+ find_fil
35
+ validate
36
+ end
37
+
38
+ def find_dir
39
+ if dir.nil?
40
+ @options[:dir] = Dir.pwd
41
+ else
42
+ raise "No such directory: #{dir}." unless File.directory?(dir)
43
+
44
+ @options[:dir] = File.expand_path(dir)
45
+ end
46
+ @options[:out] = File.expand_path(out.nil? ? '~' : out)
47
+ end
48
+
49
+ def find_fil
50
+ @files = Dir.glob("#{dir}/*.{#{EXT}}").select { |f| File.file? f }
51
+ sub = !mp3?
52
+ @files += Dir.glob("#{dir}/*").select { |f| File.directory? f } if sub
53
+ @files.sort_by!(&:naturalized)
54
+ @files.sort_by!(&:swapcase)
55
+ end
56
+
57
+ def validate
58
+ validate_files
59
+ validate_tit
60
+ validate_val(aud, :aud)
61
+ validate_val(sub, :sub)
62
+ raise "Width of the table should exeeds 14 symbols: #{wid}." if wid < 15
63
+ end
64
+
65
+ def validate_files
66
+ raise "#{dir} doesn't have #{EXT} files or directories." if files.empty?
67
+
68
+ bad = files.reject { |f| File.readable?(f) }
69
+ raise "Unable to read #{bad} files." unless bad.empty?
70
+ end
71
+
72
+ def validate_tit # rubocop:disable Metrics/AbcSize
73
+ if tit.nil?
74
+ @options[:tit] = Array.new(files.size, '0')
75
+ return
76
+ end
77
+ if files.size == 1
78
+ @files = Array.new(tit.size, files.first)
79
+ return
80
+ end
81
+ raise "Title feature doesn't support #{files.size} files."
82
+ end
83
+
84
+ def validate_val(val, tag)
85
+ f = files.size
86
+ (@options[tag] = Array.new(f, '0')).nil? || return if val.nil?
87
+ s = val.size
88
+ if s == 1
89
+ @options[tag] = Array.new(f, val.first)
90
+ else
91
+ raise "#{tag} and files do not suit #{s} != #{f}." unless s == f
92
+ end
93
+ end
94
+
95
+ def act?
96
+ @options[:act]
97
+ end
98
+
99
+ def sca?
100
+ @options[:sca]
101
+ end
102
+
103
+ def mp3?
104
+ @options[:mp3]
105
+ end
106
+
107
+ def dir
108
+ @options[:dir]
109
+ end
110
+
111
+ def tit
112
+ @options[:tit]
113
+ end
114
+
115
+ def out
116
+ @options[:out]
117
+ end
118
+
119
+ def aud
120
+ @options[:aud]
121
+ end
122
+
123
+ def sub
124
+ @options[:sub]
125
+ end
126
+
127
+ def wid
128
+ if @options[:wid].nil?
129
+ # Reads current terminal width.
130
+ wid = `tput cols`
131
+ wid.to_s.empty? ? 79 : wid.to_i
132
+ else
133
+ @options[:wid].to_i
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vi:ts=2 sw=2 tw=79 et lbr wrap
4
+ # Copyright 2018-present David Rabkin
5
+
6
+ require 'terminal-table'
7
+ require_relative 'utils'
8
+
9
+ module Transcode
10
+ # Formats and prints output data.
11
+ class Reporter
12
+ def initialize(act, tit, wid)
13
+ @act = act
14
+ @tit = tit
15
+ @tbl = wid
16
+ @ttl = @tbl - 4
17
+ @str = (@tbl - 7) / 2
18
+ @row = []
19
+ @tim = Timer.new
20
+ @sta = { converted: 0, failed: 0 }
21
+ end
22
+
23
+ def add(file, res, aud = 0, sub = 0, tit = 0)
24
+ row = [Utils.trim(File.basename(file), @str)]
25
+ if aud != 0 || sub != 0
26
+ (row << [
27
+ { value: aud, alignment: :right },
28
+ { value: sub, alignment: :right },
29
+ { value: tit, alignment: :right }
30
+ ]).flatten!
31
+ end
32
+ @row << row
33
+ @sta[res ? :converted : :failed] += 1
34
+ end
35
+
36
+ def head
37
+ head = [{ value: 'file', alignment: :center }]
38
+ if @row.first.size == 4
39
+ (head << [
40
+ { value: 'audio', alignment: :center },
41
+ { value: 'subtitles', alignment: :center },
42
+ { value: 'titles', alignment: :center }
43
+ ]).flatten!
44
+ end
45
+ head
46
+ end
47
+
48
+ def table
49
+ Terminal::Table.new(
50
+ title: Utils.trim(@tit, @ttl),
51
+ headings: head,
52
+ rows: @row,
53
+ style: { width: @tbl }
54
+ )
55
+ end
56
+
57
+ def do
58
+ msg = "#{@act ? 'Real' : 'Test'}:#{stat} in #{@tim.read}."
59
+ msg = Utils.trim(msg, @ttl)
60
+ puts "#{table}\n| #{msg}#{' ' * (@ttl - msg.length)} |\n+-#{'-' * @ttl}-+"
61
+ end
62
+
63
+ def stat
64
+ out = ''
65
+ @sta.each do |k, v|
66
+ out += ' ' + v.to_s + ' ' + k.to_s + ',' if v.positive?
67
+ end
68
+ out.chop
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vi:ts=2 sw=2 tw=79 et lbr wrap
4
+ # Copyright 2018-present David Rabkin
5
+
6
+ require 'English'
7
+ require 'shellwords'
8
+ require_relative 'configurator'
9
+ require_relative 'reporter'
10
+
11
+ module Transcode
12
+ # Transcodes any video file to m4v format.
13
+ class Transcoder
14
+ def initialize
15
+ @cfg = Configuration.new
16
+ @rep = Reporter.new(@cfg.act?, "#{@cfg.dir} -> #{@cfg.out}", @cfg.wid)
17
+ end
18
+
19
+ # Runs command and prints output instantly. Returns true on success.
20
+ def run(cmd)
21
+ cmd += ' 2>&1'
22
+ puts "Run: #{cmd}."
23
+ IO.popen(cmd).each do |line|
24
+ puts line.chomp
25
+ end.close
26
+ $CHILD_STATUS.success?
27
+ end
28
+
29
+ def m4v_cmd(file, aud, sub, tit)
30
+ c = 'transcode-video --m4v --no-log --preset veryslow'\
31
+ " --output #{@cfg.out}"
32
+ unless tit == '0'
33
+ c += "/#{File.basename(file.shellescape)}-#{tit}.m4v"
34
+ c += " --title #{tit}"
35
+ end
36
+ c += " --main-audio #{aud}" unless aud == '0'
37
+ c += " --burn-subtitle #{sub}" unless sub == '0'
38
+ c + " #{file.shellescape}"
39
+ end
40
+
41
+ # Converts files, audibale, subtitles and titles arrays to array:
42
+ # [ file1 [ aud1, sub1, tit1 ] ]
43
+ # [ file2 [ aud2, sub2, tit2 ] ]
44
+ def data
45
+ @data ||= @cfg.files.zip([@cfg.aud, @cfg.sub, @cfg.tit].transpose)
46
+ end
47
+
48
+ def m4v
49
+ data.each do |f, as|
50
+ res = @cfg.act? ? run(m4v_cmd(f, as[0], as[1], as[2])) : true
51
+ @rep.add(f, res, as[0], as[1], as[2])
52
+ end
53
+ end
54
+
55
+ def mp3_cmd(file)
56
+ file = file.shellescape
57
+ "ffmpeg -i #{file} -vn -ar 44100 -ac 2 -ab 192k -f mp3 "\
58
+ "#{@cfg.out}/#{File.basename(file, '.*')}.mp3"
59
+ end
60
+
61
+ def scn_cmd(file)
62
+ "transcode-video --scan #{file.shellescape}"
63
+ end
64
+
65
+ def do # rubocop:disable Metrics/AbcSize
66
+ if @cfg.mp3?
67
+ @cfg.files.each { |f| @rep.add(f, @cfg.act? ? run(mp3_cmd(f)) : true) }
68
+ elsif @cfg.sca?
69
+ @cfg.files.each { |f| @rep.add(f, run(scn_cmd(f))) }
70
+ else
71
+ m4v
72
+ end
73
+ @rep.do
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vi:ts=2 sw=2 tw=79 et lbr wrap
4
+ # Copyright 2018-present David Rabkin
5
+
6
+ # All methods are static.
7
+ class Utils
8
+ class << self
9
+ SEP = '~'
10
+ def trim(src, lim)
11
+ return src if src.length <= lim
12
+
13
+ beg = fin = (lim - SEP.length) / 2
14
+ beg -= 1 if lim.odd?
15
+ src[0..beg] + SEP + src[-fin..-1]
16
+ end
17
+ end
18
+ end
19
+
20
+ # Returns string with humanized time interval.
21
+ class Timer
22
+ DIC = [
23
+ [60, :seconds, :second],
24
+ [60, :minutes, :minute],
25
+ [24, :hours, :hour],
26
+ [1000, :days, :day]
27
+ ].freeze
28
+
29
+ def initialize
30
+ @sta = Time.now
31
+ end
32
+
33
+ def read
34
+ humanize(Time.now - @sta)
35
+ end
36
+
37
+ def humanize(sec)
38
+ return 'less than a second' if sec < 1
39
+
40
+ DIC.map do |cnt, nms, nm1|
41
+ next if sec <= 0
42
+
43
+ sec, n = sec.divmod(cnt)
44
+ "#{n.to_i} #{n.to_i != 1 ? nms : nm1}"
45
+ end.compact.reverse.join(' ')
46
+ end
47
+ end
48
+
49
+ # Adds natural sort method. This converts something like "Filename 10" into a
50
+ # simple array with floats in place of numbers [ "Filename", 10.0 ]. See:
51
+ # https://stackoverflow.com/questions/4078906/is-there-a-natural-sort-by-method-for-ruby
52
+ class String
53
+ def naturalized
54
+ scan(/[^\d\.]+|[\d\.]+/).collect { |f| f.match(/\d+(\.\d+)?/) ? f.to_f : f }
55
+ end
56
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vi:ts=2 sw=2 tw=79 et lbr wrap
4
+ # Copyright 2018-present David Rabkin
5
+
6
+ module Transcode
7
+ VERSION = '1.0.1'
8
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/lib')
4
+
5
+ require 'transcode'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'transcode'
9
+ s.version = Transcode::VERSION
10
+ s.required_ruby_version = '>= 2.3'
11
+ s.summary = 'Tools to transcode batch of video files.'
12
+ s.description = <<-HERE
13
+ Transcode is a wraper on Video Transcoding.
14
+ HERE
15
+ s.license = 'BSD-2-Clause'
16
+ s.author = 'David Rabkin'
17
+ s.email = 'pub@rabkin.co.il'
18
+ s.homepage = 'https://github.com/rdavid/transcode'
19
+ s.files = Dir['{bin,lib}/**/*'] + Dir['[A-Z]*'] + ['transcode.gemspec']
20
+ s.executables = ['transcode']
21
+ s.extra_rdoc_files = ['LICENSE', 'README.md']
22
+ s.require_paths = ['lib']
23
+ s.add_runtime_dependency 'pidfile', '0.3.0'
24
+ s.add_runtime_dependency 'terminal-table', '1.8.0'
25
+ s.add_runtime_dependency 'video_transcoding', '0.25.3'
26
+ s.add_development_dependency 'rubocop', '0.76.0'
27
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: transcode
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - David Rabkin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pidfile
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: terminal-table
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.8.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: video_transcoding
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.25.3
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.25.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.76.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.76.0
69
+ description: " Transcode is a wraper on Video Transcoding.\n"
70
+ email: pub@rabkin.co.il
71
+ executables:
72
+ - transcode
73
+ extensions: []
74
+ extra_rdoc_files:
75
+ - LICENSE
76
+ - README.md
77
+ files:
78
+ - LICENSE
79
+ - README.md
80
+ - bin/transcode
81
+ - lib/transcode.rb
82
+ - lib/transcode/configurator.rb
83
+ - lib/transcode/reporter.rb
84
+ - lib/transcode/transcoder.rb
85
+ - lib/transcode/utils.rb
86
+ - lib/transcode/version.rb
87
+ - transcode.gemspec
88
+ homepage: https://github.com/rdavid/transcode
89
+ licenses:
90
+ - BSD-2-Clause
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '2.3'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubygems_version: 3.1.4
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Tools to transcode batch of video files.
111
+ test_files: []