mkvtoolhelper 0.0.1 → 1.0.0
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 +4 -4
- data/bin/mkvtoolhelper +268 -4
- metadata +2 -4
- data/lib/mkvtoolhelper.rb +0 -1
- data/lib/mkvtoolhelper/version.rb +0 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d18de65cbc9d137ceb2a8595da11b4e67fbba8d
|
|
4
|
+
data.tar.gz: bea84ad47e72d198c955d9a0b76bbf1604d257d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 14a989c73a186e83cf87049efeb7c5e1b0a13ea27dddd0eb60220bc1ec011504704e7f8e05ff66363522af514600cfc84d4c3ce2f122534a2112ba7edc450740
|
|
7
|
+
data.tar.gz: 62c6fc1a6f8d60b8964276d183b841c05732e14ca25f86a69baa2bce43a3cc970722db19654c41b4ba679ed59474e4686db858f11785e51bf71a78002fe25f52
|
data/bin/mkvtoolhelper
CHANGED
|
@@ -1,13 +1,277 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# TODO: remove pry
|
|
4
|
+
require 'pry'
|
|
4
5
|
|
|
5
|
-
require '
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'open3'
|
|
8
|
+
require 'json'
|
|
9
|
+
require 'readline'
|
|
10
|
+
|
|
11
|
+
def rotate_backups f
|
|
12
|
+
i = 0
|
|
13
|
+
i += 1 while File.exist?(f.path + ".backup#{i}")
|
|
14
|
+
i.downto(1) { |i| FileUtils.mv "#{f.path}.backup#{i - 1}", "#{f.path}.backup#{i}" }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def delete_backups f
|
|
18
|
+
i = 0
|
|
19
|
+
while File.exist?(f.path + ".backup#{i}") do
|
|
20
|
+
FileUtils.rm "#{f.path}.backup#{i}"
|
|
21
|
+
i += 1
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Mkv_File
|
|
26
|
+
|
|
27
|
+
class Audio_Track
|
|
28
|
+
|
|
29
|
+
def initialize track_data
|
|
30
|
+
@id = track_data['id']
|
|
31
|
+
@codec = track_data['codec']
|
|
32
|
+
@default = track_data['properties']['default_track']
|
|
33
|
+
@name = track_data['properties']['track_name']
|
|
34
|
+
@language = track_data['properties']['language']
|
|
35
|
+
@codec_id = track_data['properties']['codec_id']
|
|
36
|
+
@uid = track_data['properties']['uid']
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def ==(other)
|
|
40
|
+
@id == other.id &&
|
|
41
|
+
@codec == other.codec &&
|
|
42
|
+
@default == other.default &&
|
|
43
|
+
@name == other.name &&
|
|
44
|
+
@language == other.language &&
|
|
45
|
+
@codec_id == other.codec_id &&
|
|
46
|
+
@uid == other.uid
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def hash
|
|
50
|
+
@id.hash &
|
|
51
|
+
@codec.hash &
|
|
52
|
+
@default.hash &
|
|
53
|
+
@name.hash &
|
|
54
|
+
@language.hash &
|
|
55
|
+
@codec_id.hash &
|
|
56
|
+
@uid.hash
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_s
|
|
60
|
+
"#<Audio_Track::#{@name} (#{@language})"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def get_tracks file
|
|
64
|
+
file.audio
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
attr_reader :id, :default, :name, :language, :codec, :codec_id, :uid
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class Subtitles_Track
|
|
72
|
+
|
|
73
|
+
def initialize track_data
|
|
74
|
+
@id = track_data['id']
|
|
75
|
+
@default = track_data['properties']['default_track']
|
|
76
|
+
@name = track_data['properties']['track_name']
|
|
77
|
+
@language = track_data['properties']['language']
|
|
78
|
+
@uid = track_data['properties']['uid']
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def ==(other)
|
|
82
|
+
@id == other.id &&
|
|
83
|
+
@default == other.default &&
|
|
84
|
+
@name == other.name &&
|
|
85
|
+
@language == other.language &&
|
|
86
|
+
@uid == other.uid
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def hash
|
|
90
|
+
@id.hash &
|
|
91
|
+
@default.hash &
|
|
92
|
+
@name.hash &
|
|
93
|
+
@language.hash &
|
|
94
|
+
@uid.hash
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def to_s
|
|
98
|
+
"#<Subtitles_Track::#{@name} (#{@language})"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def get_tracks file
|
|
102
|
+
file.subtitles
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
attr_reader :id, :default, :name, :language, :uid
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def initialize path
|
|
109
|
+
@path = path
|
|
110
|
+
identify!
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def identify!
|
|
114
|
+
json = invoke_and_get_output %W{mkvmerge --identify-verbose -J #{@path}}
|
|
115
|
+
@data = JSON.parse json
|
|
116
|
+
get_audio_tracks!
|
|
117
|
+
get_subtitle_tracks!
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def to_s
|
|
121
|
+
"#<Mkv_File::#{@path}>"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
attr_reader :audio, :subtitles, :path
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
def invoke_and_get_output args
|
|
128
|
+
o, s = Open3.capture2e(*args)
|
|
129
|
+
raise "Invoke failed: #{o}" unless s.success?
|
|
130
|
+
o
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def get_audio_tracks!
|
|
134
|
+
@audio = []
|
|
135
|
+
@data['tracks'].select { |t| t['type'] == 'audio' }.each do |t|
|
|
136
|
+
@audio << Audio_Track.new(t)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def get_subtitle_tracks!
|
|
141
|
+
@subtitles = []
|
|
142
|
+
@data['tracks'].select { |t| t['type'] == 'subtitles' }.each do |t|
|
|
143
|
+
@subtitles << Subtitles_Track.new(t)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
class Command
|
|
150
|
+
def initialize accepts, desc
|
|
151
|
+
@accepts = accepts
|
|
152
|
+
@desc = desc
|
|
153
|
+
@action = Proc.new
|
|
154
|
+
end
|
|
155
|
+
def accepts line
|
|
156
|
+
@line = line
|
|
157
|
+
@match = line.match(@accepts)
|
|
158
|
+
end
|
|
159
|
+
def action!
|
|
160
|
+
@action.call @match
|
|
161
|
+
end
|
|
162
|
+
def to_s
|
|
163
|
+
"#{@accepts.inspect} - #{@desc}"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
$commands = []
|
|
167
|
+
$commands << Command.new(/^h(elp)?$/, "print help") do
|
|
168
|
+
puts "Available commands:"
|
|
169
|
+
$temp_commands.each { |c| puts c }
|
|
170
|
+
end
|
|
171
|
+
$commands << Command.new(/^q(uit)?$/, "quit program") { exit }
|
|
172
|
+
$commands << Command.new(/^n(ext)?$/, "continue to next group") { throw :next }
|
|
173
|
+
$commands << Command.new(/^d(efault)? ?(?<id>\d+)$/, "set as default") do |m|
|
|
174
|
+
old_id = $kit[:get_tracks].call($group.first).select { |t| t.default }.first&.id
|
|
175
|
+
new_id = m[:id].to_i
|
|
176
|
+
if old_id == new_id
|
|
177
|
+
puts "Track #{new_id} is already default."
|
|
178
|
+
next
|
|
179
|
+
end
|
|
180
|
+
old_uid = $kit[:get_tracks].call($group.first).find { |t| t.id == old_id }.uid
|
|
181
|
+
new_uid = $kit[:get_tracks].call($group.first).find { |t| t.id == new_id }.uid
|
|
182
|
+
$group.each do |f|
|
|
183
|
+
rotate_backups f
|
|
184
|
+
raise "Cannot backup!" unless system(*%W{cp --reflink=alway #{f.path} #{f.path}.backup0})
|
|
185
|
+
cmd = %W{mkvpropedit #{f.path}}
|
|
186
|
+
if old_id
|
|
187
|
+
cmd += %W{--edit track:=#{old_uid} --set flag-default=0}
|
|
188
|
+
end
|
|
189
|
+
cmd += %W{--edit track:=#{new_uid} --set flag-default=1}
|
|
190
|
+
o, s = Open3.capture2e(*cmd)
|
|
191
|
+
raise "Failed!: #{o}" unless s.success?
|
|
192
|
+
f.identify!
|
|
193
|
+
puts "Done: #{File.basename f.path}"
|
|
194
|
+
end
|
|
195
|
+
print_group $group
|
|
196
|
+
$kit[:print].call($group)
|
|
197
|
+
end
|
|
198
|
+
$commands << Command.new(/^d(elete backups)?$/, "delete backups") do |m|
|
|
199
|
+
$group.each do |f|
|
|
200
|
+
delete_backups f
|
|
201
|
+
puts "Deleted backups for #{File.basename f.path}"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def print_group group
|
|
206
|
+
puts "Files in this group:"
|
|
207
|
+
group.each { |f| puts " #{f.path}" }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def print_audio group
|
|
211
|
+
file = group.first
|
|
212
|
+
puts "Audio tracks in this group:"
|
|
213
|
+
file.audio.each do |a|
|
|
214
|
+
puts " #{a.default ? ?* : ' '} #{a.id}: #{a.name} (#{a.language}, #{a.codec})"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
audio_kit = {
|
|
219
|
+
print: method(:print_audio),
|
|
220
|
+
commands: [],
|
|
221
|
+
get_tracks: ->(f) { f.audio },
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
def print_subtitles group
|
|
225
|
+
file = group.first
|
|
226
|
+
puts "Subtitles tracks in this group:"
|
|
227
|
+
file.subtitles.each do |a|
|
|
228
|
+
puts " #{a.default ? ?* : ' '} #{a.id}: #{a.name} (#{a.language})"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
subtitles_kit = {
|
|
233
|
+
print: method(:print_subtitles),
|
|
234
|
+
commands: [],
|
|
235
|
+
get_tracks: ->(f) { f.subtitles },
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
def process_groups groups, kit
|
|
239
|
+
begin
|
|
240
|
+
$kit = kit
|
|
241
|
+
commands = [*kit[:commands], *$commands]
|
|
242
|
+
$temp_commands = commands
|
|
243
|
+
groups.each do |group|
|
|
244
|
+
$group = group
|
|
245
|
+
print_group group
|
|
246
|
+
kit[:print].call group
|
|
247
|
+
catch (:next) do
|
|
248
|
+
while line = Readline.readline("> ", true)
|
|
249
|
+
accepting = commands.select { |c| c.accepts line }
|
|
250
|
+
# sanity check
|
|
251
|
+
raise "Multiple commands for '#{line}'" if accepting.size > 1
|
|
252
|
+
if accepting.empty?
|
|
253
|
+
puts "No matching command."
|
|
254
|
+
else
|
|
255
|
+
accepting.first.action!
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
ensure
|
|
261
|
+
$group = nil
|
|
262
|
+
$kit = nil
|
|
263
|
+
end
|
|
264
|
+
end
|
|
6
265
|
|
|
7
266
|
begin
|
|
8
|
-
|
|
267
|
+
files = Dir.glob('**/*.mkv').map { |f| Mkv_File.new f }
|
|
268
|
+
by_audio = files.group_by { |f| f.audio.hash }.collect { |k, v| v }
|
|
269
|
+
by_subtitles = files.group_by { |f| f.subtitles.hash }.collect { |k, v| v }
|
|
270
|
+
|
|
271
|
+
process_groups by_audio, audio_kit
|
|
272
|
+
process_groups by_subtitles, subtitles_kit
|
|
9
273
|
rescue => e
|
|
10
|
-
STDERR.puts e.message
|
|
274
|
+
STDERR.puts "Exception: #{e.message}"
|
|
11
275
|
STDERR.puts
|
|
12
276
|
STDERR.puts "Backtrace:"
|
|
13
277
|
STDERR.puts e.backtrace
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mkvtoolhelper
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gray Wolf
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-08-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
The cli interface of mkvtool isn't the best for batch editing
|
|
@@ -21,8 +21,6 @@ extensions: []
|
|
|
21
21
|
extra_rdoc_files: []
|
|
22
22
|
files:
|
|
23
23
|
- bin/mkvtoolhelper
|
|
24
|
-
- lib/mkvtoolhelper.rb
|
|
25
|
-
- lib/mkvtoolhelper/version.rb
|
|
26
24
|
homepage: https://github.com/graywolf/mkvtoolhelper
|
|
27
25
|
licenses:
|
|
28
26
|
- MIT
|
data/lib/mkvtoolhelper.rb
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
require 'mkvtoolhelper/version'
|