forked from linux4ever07/scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
merge_subs.sh
executable file
·345 lines (269 loc) · 7.91 KB
/
merge_subs.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#!/bin/bash
# This script is meant to mux extra subtitles into an MKV (Matroska)
# file, and also set the default subtitle. It asks the user to select
# the default subtitle from a menu.
# Even if the video file given as first argument is not an MKV, it will
# still probably be able to be processed, as long as 'mkvmerge' supports
# it. The output file will be MKV no matter what.
# All video / subtitle files passed to the script that are not Matroska
# will be temporarily converted to Matroska so their metadata can be
# read by 'mkvinfo'. The temporary files are deleted after the script is
# done.
if=$(readlink -f "$1")
session="${RANDOM}-${RANDOM}"
of="${if%.[^.]*}-${session}.mkv"
files_n=0
sub_tracks_n=0
declare default
declare -a files files_tmp args full_args range1 range2
declare -A sub_tracks
regex_start='^\|\+ Tracks$'
regex_stop='^\|\+ '
regex_strip='^\| +\+ (.*)$'
regex_track="^Track$"
regex_num="^Track number: [0-9]+ \(track ID for mkvmerge & mkvextract: ([0-9]+)\)$"
regex_sub="^Track type: subtitles$"
regex_lang="^Language( \(.*\)){0,1}: (.*)$"
regex_name="^Name: (.*)$"
regex_fn='^(.*)\.([^.]*)$'
regex_lang_arg='^[[:alpha:]]{3}$'
mapfile -t cmd < <(command -v mkvinfo mkvmerge)
if [[ ${#cmd[@]} -ne 2 ]]; then
printf '\nThis script needs %s installed!\n\n' 'mkvtoolnix'
exit
fi
# Creates a function called 'usage', which will print usage instructions
# and then quit.
usage () {
cat <<USAGE
Usage: $(basename "$0") [mkv] [srt] [args] [...]
Optional arguments:
[srt]
File name of subtitle to be added (needs to be specified before the
other arguments).
-lang [code]
Three-letter language code for added subtitle.
-name [...]
Name for added subtitle.
USAGE
exit
}
# Creates a function called 'get_tracks', which will read the metadata
# of media files, and if they contain subtitle tracks, store those in
# the 'sub_tracks' hash.
get_tracks () {
if_tmp=$(readlink -f "$1")
bn_tmp=$(basename "$if_tmp")
dn_tmp=$(dirname "$if_tmp")
declare ext_tmp of_tmp
declare -a mkvinfo_tracks
declare -A tracks
# Parses the input file name, and separates basename from extension.
# If this fails, return from the function.
if [[ ${bn_tmp,,} =~ $regex_fn ]]; then
match=("${BASH_REMATCH[@]:1}")
ext_tmp="${match[1]}"
session_tmp="${RANDOM}-${RANDOM}"
of_tmp="${dn_tmp}/${match[0]}-tmp-${session_tmp}.mkv"
else
return
fi
# If input file is not a Matroska file, remux it to a temporary MKV,
# add the file name to the 'files_tmp' array, so it can be deleted
# later.
if [[ $ext_tmp != 'mkv' ]]; then
printf '\nRemuxing: %s\n' "$if_tmp"
mapfile -t mkvmerge_lines < <(mkvmerge -o "$of_tmp" "$if_tmp" 2>&1)
if [[ $? -ne 0 ]]; then
printf '%s\n' "${mkvmerge_lines[@]}"
printf '\n'
exit
fi
files_tmp+=("$of_tmp")
if_tmp="$of_tmp"
fi
# Adds file name to the 'files' array, so it can be used later to
# construct the mkvmerge command.
files["${files_n}"]="$if_tmp"
mapfile -t mkvinfo_lines < <(mkvinfo "$if_tmp" 2>&-)
# Singles out the part that lists the tracks, and ignores the rest of
# the output from 'mkvinfo'.
switch=0
for (( i = 0; i < ${#mkvinfo_lines[@]}; i++ )); do
line="${mkvinfo_lines[${i}]}"
if [[ $line =~ $regex_start ]]; then
switch=1
continue
fi
if [[ $switch -eq 1 ]]; then
if [[ $line =~ $regex_stop ]]; then
switch=0
break
fi
if [[ $line =~ $regex_strip ]]; then
line="${BASH_REMATCH[1]}"
fi
mkvinfo_tracks+=("$line")
fi
done
unset -v mkvinfo_lines
# Gets all tracks from Matroska file.
tracks_n=0
for (( i = 0; i < ${#mkvinfo_tracks[@]}; i++ )); do
line="${mkvinfo_tracks[${i}]}"
if [[ $line =~ $regex_track ]]; then
tracks_n=$(( tracks_n + 1 ))
tracks["${tracks_n},sub"]=0
fi
if [[ $line =~ $regex_num ]]; then
tracks["${tracks_n},num"]="${BASH_REMATCH[1]}"
fi
if [[ $line =~ $regex_sub ]]; then
tracks["${tracks_n},sub"]=1
fi
# For some tracks, the language can be listed twice. First with a
# three-letter code, and then with a two-letter code. The first code is
# preferred by this script.
if [[ $line =~ $regex_lang ]]; then
if [[ -z ${tracks[${tracks_n},lang]} ]]; then
tracks["${tracks_n},lang"]="${BASH_REMATCH[2]}"
fi
fi
if [[ $line =~ $regex_name ]]; then
tracks["${tracks_n},name"]="${BASH_REMATCH[1]}"
fi
done
tracks_n=$(( tracks_n + 1 ))
unset -v mkvinfo_tracks
# Gets all subtitle tracks specifically.
for (( i = 1; i < tracks_n; i++ )); do
sub_tmp="${tracks[${i},sub]}"
num_tmp="${tracks[${i},num]}"
lang_tmp="${tracks[${i},lang]}"
name_tmp="${tracks[${i},name]}"
if [[ $sub_tmp -eq 1 ]]; then
sub_tracks_n=$(( sub_tracks_n + 1 ))
else
continue
fi
if [[ -n $num_tmp ]]; then
sub_tracks["${sub_tracks_n},num"]="$num_tmp"
fi
if [[ -n $lang_tmp ]]; then
if [[ -z ${sub_tracks[${sub_tracks_n},lang]} ]]; then
sub_tracks["${sub_tracks_n},lang"]="$lang_tmp"
fi
fi
if [[ -n $name_tmp ]]; then
if [[ -z ${sub_tracks[${sub_tracks_n},name]} ]]; then
sub_tracks["${sub_tracks_n},name"]="$name_tmp"
fi
fi
done
}
# The loop below handles the arguments to the script.
while [[ $# -gt 0 ]]; do
case "$1" in
'-lang')
shift
if [[ $1 =~ $regex_lang_arg ]]; then
sub_tracks["${sub_tracks_n},lang"]="${1,,}"
else
usage
fi
shift
;;
'-name')
shift
sub_tracks["${sub_tracks_n},name"]="$1"
shift
;;
*)
if [[ -f $1 ]]; then
files_n=$(( files_n + 1 ))
range1["${files_n}"]=$(( sub_tracks_n + 1 ))
get_tracks "$1"
range2["${files_n}"]=$(( sub_tracks_n + 1 ))
shift
else
usage
fi
;;
esac
done
# If no subtitles have been found, quit.
if [[ ${#sub_tracks[@]} -eq 0 ]]; then
usage
fi
# Adds 1 to $files_n and $sub_tracks_n, so we can loop through all the
# elements. Otherwise, the last element will be skipped.
files_n=$(( files_n + 1 ))
sub_tracks_n=$(( sub_tracks_n + 1 ))
# Prints all the subtitle tracks, and asks the user to choose the
# default track, saves that choice in the $default variable.
printf '\n%s\n\n' 'Choose the default track:'
for (( i = 1; i < files_n; i++ )); do
range1_tmp="${range1[${i}]}"
range2_tmp="${range2[${i}]}"
for (( j = range1_tmp; j < range2_tmp; j++ )); do
lang_tmp="${sub_tracks[${j},lang]}"
name_tmp="${sub_tracks[${j},name]}"
printf '%s)\n' "$j"
printf ' file: %s\n' "${files[${i}]}"
printf ' language: %s\n' "$lang_tmp"
printf ' name: %s\n' "$name_tmp"
done
done
printf '\n'
until [[ -n $default ]]; do
read -p '>'
if [[ ! $REPLY =~ ^[0-9]+$ ]]; then
continue
fi
if [[ -n ${sub_tracks[${REPLY},num]} ]]; then
default="$REPLY"
fi
done
printf '\nDefault subtitle track: %s\n' "$default"
printf '(Track ID: %s)\n\n' "${sub_tracks[${default},num]}"
# Puts together the mkvmerge command. The loop below deals with
# subtitles that are in the Matroska file, and the subtitle files given
# as arguments to the script. The loop makes sure a file name can only
# be listed once. This is for when a subtitle file has multiple subtitle
# tracks.
for (( i = 1; i < files_n; i++ )); do
range1_tmp="${range1[${i}]}"
range2_tmp="${range2[${i}]}"
declare -a args_tmp
for (( j = range1_tmp; j < range2_tmp; j++ )); do
num_tmp="${sub_tracks[${j},num]}"
lang_tmp="${sub_tracks[${j},lang]}"
name_tmp="${sub_tracks[${j},name]}"
if [[ $i -ne 1 ]]; then
if [[ -n $lang_tmp ]]; then
args_tmp+=('--language' \""${num_tmp}:${lang_tmp}"\")
fi
if [[ -n $name_tmp ]]; then
args_tmp+=('--track-name' \""${num_tmp}:${name_tmp}"\")
fi
fi
if [[ $j -eq $default ]]; then
args_tmp+=('--default-track-flag' \""${num_tmp}:1"\")
else
args_tmp+=('--default-track-flag' \""${num_tmp}:0"\")
fi
done
args_tmp+=(\""${files[${i}]}"\")
args+=("${args_tmp[@]}")
unset -v args_tmp
done
full_args=(mkvmerge -o \""$of"\" "${args[@]}")
# Runs mkvmerge.
eval "${full_args[@]}"
# Removes temporary MKV files.
if [[ ${#files_tmp[@]} -gt 0 ]]; then
rm -f "${files_tmp[@]}"
fi
# Prints the mkvmerge command.
string="${full_args[@]}"
printf '\n%s\n\n' "$string"