-
Notifications
You must be signed in to change notification settings - Fork 0
/
silenceremove.sh
227 lines (186 loc) · 8.69 KB
/
silenceremove.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
#!/bin/bash
# 1) identifies and extract 'in' & 'out' points to trim restored transcription disc recordings (wav) using the silencedetect audio filter
# 2) inputs the 'in' & 'out' points into a temp. xml document as wav cue information
# 3) uses bwf metaedit to import the 'in' & 'out' points through the xml into the desired wav file
# written by matthew yang with contributions from sarah wardrop, feb 2024
# color codes for messages
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m'
_usage(){
echo -e "${GREEN}\nThis script identifies the 'in' & 'out' points to trim restored transcription disc recordings and appends the timestamps into the cue chunk.\n${NC}"
}
# checks for wav header in file
check_wav_header() {
local wav_file="$1"
# rejects if it's not a file
if [[ ! -f "$wav_file" ]]; then
echo -e "${RED}[$(basename "$wav_file")]Error: Is not a file. Aborting...${NC}"
return 1
fi
# reads 44 bytes of header
header=$(xxd -l 44 -g 1 "$wav_file")
riff_value="${header:9:12}"
wavefmt_value="${header:33:12}"
# if it's a file, checks for RIFF and WAV header
if [[ "$riff_value" == " 52 49 46 46" && "$wavefmt_value" == " 57 41 56 45" ]]; then
echo -e "${GREEN}[$(basename "$wav_file")]WAV header is valid.${NC}"
return 0
else
echo -e "${RED}[$(basename "$wav_file")]Error: Invalid WAV header. Aborting...${NC}"
return 1
fi
}
# checks wav file's sample rate
get_sample_rate() {
local wav_file="$1"
local sample_rate=$(ffprobe -v error -select_streams a -of default=noprint_wrappers=1:nokey=1 -show_entries stream=sample_rate "$wav_file")
echo "$sample_rate"
}
# convert seconds to mins & seconds
get_timecode() {
local input_seconds=$1
# calculate hours, minutes, and seconds using bc for floating-point arithmetic
local hours=$(echo "$input_seconds / 3600" | bc)
local minutes=$(echo "($input_seconds % 3600) / 60" | bc)
local seconds=$(echo "$input_seconds % 60" | bc)
# format the result directly without the need for additional variables
printf "%02d:%02d:%05.2f\n" $hours $minutes $seconds
}
# identifies in & out timecode to trim transcription disc recordings using the silencedetect audio filter and samples it from seconds
get_silence_timecode() {
local wav_file="$1"
local sample_rate=$(get_sample_rate "$wav_file")
# calculates silence start & end timestamps using the ffmpeg adeclick & silencedetect audio filter ffmpeg script by sarah wardrop
echo -e "${GREEN}[$(basename "$wav_file")]Identifying 'in' & 'out' points...${NC}"
# silence_graph=$(ffmpeg -i "$wav_file" -filter_complex \
# "[0:a]showwaves=s=1280x720:mode=line,format=yuv420p[v]" \
# -map "[v]" -map 0:a -c:v libx264 -c:a copy "$(basename "$wav_file" .wav)".mkv)
silence_info=$(ffmpeg -hide_banner -i "$wav_file" -ac 1 -filter_complex \
"adeclick=window=55:overlap=75[DC1]; \
[DC1]acrossover=split=1500 8000:order=20th[LOW][MID][HIGH]; \
[LOW]adeclick=window=55:overlap=75[LOW1]; \
[MID]adeclick=window=55:overlap=75:t=1[MID1]; \
[HIGH]adeclick=window=55:overlap=75[HIGH1]; \
[LOW1][MID1][HIGH1]amix=inputs=3[DCMIX]; \
[DCMIX]highpass=f=60:t=s,lowpass=f=10000:t=s[silence]; \
[silence]silencedetect=n=-25dB:d=5" -f null - 2>&1 | tee /dev/tty)
# echo -e "${GREEN}[$(basename "$wav_file")]Identifying 'in' & 'out' points...${NC}"
# silence_info=$(ffmpeg -hide_banner -i "$wav_file" -af silencedetect=n=0.8 -f null - 2>&1 | tee /dev/tty)
# echoes ffmpeg output and greps the first occurence of silence_end to get the 'in' point (seconds)
in_point=$(echo "$silence_info" | grep -oE 'silence_end: [0-9.]+' | grep -oE '[0-9.]+' | head -n 1)
# echoes ffmpeg output and greps the last occurence of silence_end to get the 'out' point (seconds)
out_point=$(echo "$silence_info" | grep -oE 'silence_end: [0-9.]+' | grep -oE '[0-9.]+' | tail -n 1)
# echoes the extracted values for debugging
# echo -e "${YELLOW}in_point: $in_point, out_point: $out_point${NC}"
# check if in_point and out_point are empty
if [[ -z "$in_point" || -z "$out_point" ]]; then
echo -e "${RED}Error: No valid 'in' and 'out' points found. Aborting...${NC}"
# echo -e "${YELLOW}Debug: in_point=$in_point, out_point=$out_point${NC}"
exit 1
fi
# check if there is output to grep
if ! echo "$silence_info" | grep -oE 'silence_end: [0-9.]+' | grep -oE '[0-9.]+' | head -n 1 > /dev/null; then
echo -e "${RED}Error: No silence information found. Aborting...${NC}"
exit 1
fi
# converts the 'in' point from seconds to samples
in_point_sampled=$(echo "$in_point * $sample_rate" | bc | awk '{printf "%.0f\n", $1}')
# converts the 'out' point from seconds to samples
out_point_sampled=$(echo "$out_point * $sample_rate" | bc | awk '{printf "%.0f\n", $1}')
# converts the 'in' point from seconds to minutes & seconds
in_point_timecode=$(get_timecode "$in_point")
# converts the 'out' point from seconds to minutes & seconds
out_point_timecode=$(get_timecode "$out_point")
echo -e "${GREEN}[$(basename "$wav_file") - IN] $in_point seconds / $in_point_timecode (sample value: $in_point_sampled)${NC}"
echo -e "${GREEN}[$(basename "$wav_file") - OUT] $out_point seconds / $out_point_timecode (sample value: $out_point_sampled)${NC}"
}
# creates temporary XML file with cue information
generate_xml() {
local INPOINT=$1
local OUTPOINT=$2
local SAMPLERATE=$3
cat <<EOF
<Cues samplerate="$SAMPLERATE">
<Cue>
<ID>1</ID>
<Position>$INPOINT</Position>
<DataChunkID>0x64617461</DataChunkID>
<ChunkStart>0</ChunkStart>
<BlockStart>0</BlockStart>
<SampleOffset>130242</SampleOffset>
<Label>Presentation</Label>
<Note></Note>
<LabeledText>
<SampleLength>$(($OUTPOINT - $INPOINT))</SampleLength>
<PurposeID>0x72676E20</PurposeID>
<Country>0</Country>
<Language>0</Language>
<Dialect>0</Dialect>
<CodePage>0</CodePage>
<Text></Text>
</LabeledText>
</Cue>
</Cues>
EOF
}
# import xml using bwf metaedit
create_cue() {
local wav_file="$1"
local xml_file="$(dirname "$wav_file")/$(basename "$wav_file").cue.xml"
if [[ -s "$xml_file" ]]; then
# bwf metaedit command to initiate xml file import
xml_import=$(bwfmetaedit --in-cue-xml "$wav_file")
echo -e "${GREEN}[$(basename "$wav_file")]XML imported successfully.${NC}"
else
echo -e "${RED}[$(basename "$wav_file")]Error: XML file not present or empty. Aborting...${NC}"
exit 1
fi
}
# removes temp. xml file
remove_xml() {
local xml_file="$(dirname "$wav_file")/$(basename "$wav_file").cue.xml"
if [[ -f "$xml_file" ]]; then
rm -f "$xml_file"
echo -e "${GREEN}[$(basename "$wav_file")]Removed temporary XML file: $(basename "$xml_file").${NC}"
else
echo -e "${RED}Error: Temporary XML file not found: $(basename "$xml_file").${NC}"
fi
}
# provide script usage instruction if no input provided
if [[ $# -eq 0 ]]; then
script_name=$(basename "$0")
_usage
echo -e "Usage: $script_name <file1> <file2> <file3> ... \n"
exit 1
fi
# checks if bwf metaedit & ffmpeg & ffprobe is installed
if ! command -v bwfmetaedit &> /dev/null || ! command -v ffmpeg &> /dev/null || ! command -v ffprobe &> /dev/null || ! command -v xxd &> /dev/null ; then
echo -e "${RED}Error: bwfmetaedit/ffmpeg/ffprobe is not installed. Aborting...${NC}"
exit 1
fi
# performs check_wav_header, creates xml file, import cue with bwf metaedit
for wav_file in "$@"; do
echo "============$(basename "$wav_file")============"
if check_wav_header "$wav_file"; then
# gets wav file sample rate
SAMPLERATE=$(get_sample_rate "$wav_file")
# gets 'in' and 'out' points
get_silence_timecode "$wav_file"
# declare sampled 'in' and 'out' point
INPOINT="$in_point_sampled"
OUTPOINT="$out_point_sampled"
# creates temporary xml file that accompanies input file
xml_file="$(dirname "$wav_file")/$(basename "$wav_file").cue.xml"
generate_xml "$INPOINT" "$OUTPOINT" "$SAMPLERATE" > "$xml_file"
# imports cue information (temp. xml) into the wav file using bwf metaedit
create_cue "$wav_file"
# removes temp. xml file after succesful import
remove_xml "$xml_file"
echo -e "${GREEN}[$(basename "$wav_file")]Success! 'in' & 'out' points added in cue chunk.${NC}"
echo "============END============"
fi
done
# dave's ffprobe command
# ffprobe -f lavfi -i amovie=/Users/MatthewYang/Desktop/SDA257/HBG00733_01_transcriptiondisc.wav,silencedetect=n=-10dB:d=0.1 -show_entries frame_tags=lavfi.silence_start,lavfi.silence_duration -of compact=nk=0 | grep -v "|$"