-
Notifications
You must be signed in to change notification settings - Fork 0
/
toaac
executable file
·286 lines (248 loc) · 7.79 KB
/
toaac
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
#!/bin/sh
prog=`basename $0`
# check for -f flag to force conversion
force=0
# be verbose in output
verbose=0
# process id of this script
script_pid=$$
# try to make shell's true/false make more sense by using variables
true=0
false=1
outputDir=""
#
# had to read http://wiki.bash-hackers.org/howto/getopts_tutorial to figure out "getopts"!
#
while getopts fhvd: opt; do
case $opt in
d) outputDir="${OPTARG}/"
if ! mkdir -p "${outputDir}"; then
echo "Cannot create output dir ${outputDir}"
exit 1;
fi ;;
f) force=1 ;;
v) verbose=1 ;;
h) echo "Usage: $prog # converts all *.mkv files"
echo " $prog foo.mkv bar.mkv # only convert specified files"
echo " $prog [-f] [-h] [-v]"
echo " -f -- force conversion, even if aac already exists (adds another track)"
echo " -h -- print this help"
echo " -v -- be verbose"
exit 0
;;
esac
done
#
# remove flags from the paramater list
shift $((OPTIND-1))
# for debugging
# set -x
if [ $# = 0 ]; then
set *.[Mm][Kk][Vv]
if [ "x$*" = 'x*.mkv' ]; then
echo "${prog}: No files specified and no files match *.mkv"
exit 2
fi
fi
fileCount=0
dirCount=0
## I've been trying various flags to mix aac audio..
ff_aac_opts1="-y -vn -acodec aac -strict experimental -ac 2 -ar 44100 -ab 192k"
ff_aac_opts2="-y -vn -c:a aac -strict experimental -ac 2 -b:a 192k -scodec copy"
ff_aac_opts3='-y -vn -c:a aac -af "pan=stereo|FL=FL+0.5*FC+SL+0.5*LFE|FR=FR+0.5FC+SR+0.5*LFE" -b:a 192k -scodec copy'
## this is the one I'm trying right now
ff_aac_opts="$ff_aac_opts2"
## video options - just copy everything, and mark unknown audio as english
## (should be the aac stream; probably doesn't matter)
ff_mkv_opts="-y -map 0: -map 1: -c copy -flags +global_header -metadata:s:a:0 language=eng"
# check to make sure we have ffmpeg in our path
if which ffmpeg >/dev/null 2>&1; then
# we can run
:
else
echo "$0 requires ffmpeg in its PATH."
exit 1
fi
# "trash" is a really nice command line tool that moves files to the correct directory
# to have the same effect as dragging files ot the trash, which is nice and undoable!
#
if which trash >/dev/null 2>&1; then
# yay we can use the trash
:
else
echo "Files will not be put in the Trash without the 'trash' command available"
echo "You will be prompted for every file deletion instead"
fi
# "e" prints out a command and then exexcutes it, filtering out some extra ffmpeg output we don't want
e() {
if [ $verbose = 1 ]; then
echo "## `date` -- $@"
else
echo "## `date` -- $@" | sed -e "s/$ff_aac_opts//" -e "s/$ff_mkv_opts//"
fi
"$@" </dev/null 2>&1 | egrep -v '( lib| config| built|^ffmpeg|^Press| encoder| creation_time|Non-monotonous DTS)'
}
# "del" removes a file using either the nice "trash" command mentioned above, or interactive "rm"
# which at least asks the user if it's ok to delete the file
del() {
if which trash >/dev/null 2>&1; then
# use "trash -a" so it works even if you're logged in remotely on the command line
e trash -a "$@";
else
e rm -i "$@";
fi
}
# check to see if a file already has an aac audio stream.
# Note that shell functions returning "1" for false and "0" for true,
# the opposite of almost all other languages
needs_converting() {
file="$1"
has_ac3=0
has_aac=0
ffmpeg -i "$file" 2>&1 | grep 'Audio: ac3' > /dev/null && has_ac3=$false
ffmpeg -i "$file" 2>&1 | grep 'Audio: aac' > /dev/null && has_aac=$false
needs_it=$false
[ $has_ac3 = 1 -a $has_aac = 0 ] && needs_it=$true
[ $force = 1 ] && needs_it=$true
# echo "# needs_it=$needs_it; has_ac3=$has_ac3, has_aac=$has_aac - $file"
return $needs_it
}
assert_non_zero_size() {
file="$1"
d=($(wc -c "$file"))
if (( ${d[0]} == 0 )); then
echo "###!!! file has zero size: $file" >&2
echo "###!!! aborting.." >&2
exit 2
fi
}
larger_than() {
larger="$1"
smaller="$2"
larger_wc=($(wc -c "$larger"))
larger_size=${larger_wc[0]}
smaller_wc=($(wc -c "$smaller"))
smaller_size=${smaller_wc[0]}
largerMB="$((larger_size/1024/1024)) MB"
smallerMB="$((smaller_size/1024/1024)) MB"
if (( larger_size <= smaller_size )); then
echo "### ❌ file is smaller[$largerMB] than original[$smallerMB]: $larger" >&2
return $false
else
echo "### ✅ file is larger[$largerMB] than original[$smallerMB]: $larger" >&2
return $true
fi
}
add_aac_to_file() {
file="$1"
[ $verbose = 1 ] && echo "## add_aac_to_file $file"
# `originalFile` is the new name of the original file,
# and is the main source for the new version of `file`
ext="${file##*.}"
base="${file%.*}"
# originalFile="${file%.*}-orig.${ext}"
originalFile="${base}-orig.${ext}"
# `abortedFile` is used in case of an error
abortedFile="${base}-aborted.${ext}"
# rename `file` to `originalFile`
e mv "$file" "${originalFile}"
# `aacFile` is the aac-encoded version of the ac3 audio
# track in `originalFile`
aacFile="${originalFile%.*}".aac
#`mkvFile` is the original file with the aac track added to it
mkvFile="${outputDir}${base}.mkv"
# create `aacFile` by converting the ac3 track from `originalFile`
e ffmpeg -i "${originalFile}" $ff_aac_opts "${aacFile}"
assert_non_zero_size "${aacFile}"
# Combine the track from `aacFile` and all of `originalFile`,
# to create `file`
e ffmpeg -i "${aacFile}" -i "${originalFile}" $ff_mkv_opts "${mkvFile}"
assert_non_zero_size "${mkvFile}"
# check that the new file is **larger** than the original, which it **must** be
if larger_than "${mkvFile}" "${originalFile}"; then
# all is good, remove now-useless `aacFile` and `originalFile`
del "${originalFile}" "${aacFile}"
else
# restore file to original names, renaming aborted file to `...-aborted...`
mv "${mkvFile}" "${abortedFile}"
mv "${originalFile}" "${file}"
# should it be trashed as well? Hmm ...
echo "### ❌ aborting.." >&2
# kill the process instead of just exiting since child processes
# may be .. still processing
kill $script_pid
fi
}
# cpuIndex keeps track of which CPU we are running on; it counts down from
# the # of CPUs and when it hits zero, the script calls "wait" to wait until
# all the CPUs are freed up.
# [In future it would be better to manage this on a CPU-by-CPU basis instead]
#
cpuIndex=1
if which sysctl >/dev/null 2>&1; then
# Mac-specific way to get the # of physical CPUs
# (be conservative - "hw.ncpu" rturns max threads which includes non-physical CPUs)
cpuCount=$(sysctl hw.physicalcpu | awk '{print $2}')
# less one for zero-based counting
((cpuCount--))
cpuIndex=$cpuCount
fi
convert_if_needed() {
file=$1
if [ -d "$file" ]; then
((dirCount++))
echo "# converting files in $file"
cd "$file"
localFiles=(*.[Mm][Kk][Vv])
if [ "$localFiles" = "*.[Mm][Kk][Vv]" ]; then
echo "No mkv files in $file"
else
for localFile in *.[Mm][Kk][Vv]; do
convert_if_needed "$localFile" > /dev/null
echo "## $localFile"
done
fi
echo "# leaving $file"
echo ""
cd ..
else
((fileCount++))
[ $verbose = 1 ] && echo "## processing $file"
if needs_converting "$file"; then
if ((cpuIndex > 0)); then
add_aac_to_file "$file" &
((cpuIndex--))
# offset the threads a bit to try to make **some** sense of the output
sleep 2
else
add_aac_to_file "$file"
# wait for any background process to finish
if [ "x$(jobs)" != x ]; then
echo "## Waiting for processes: $(jobs -p)"
wait
fi
# start over with all threads
cpuIndex=$cpuCount
fi
else
echo "does not need converting: $file"
if [ "x${outputDir}" != "x" ]; then
mv "$file" "${outputDir}" || echo Cannot move "$file" to "${outputDir}"
fi
fi
fi
}
# iterate over all the files
for file; do
convert_if_needed "$file"
done
# wait for any background process to finish
if [ "x$(jobs)" != x ]; then
echo "## Waiting for processes: $(jobs -p)"
wait
fi
echo "##"
echo "##"
echo "## `date` -- `basename $0`: completed $fileCount files, $dirCount dirs"
echo "##"
echo "##"