mirror of
https://github.com/dscripka/openWakeWord.git
synced 2026-08-27 18:17:20 -04:00
145 lines
No EOL
5 KiB
Python
145 lines
No EOL
5 KiB
Python
# Copyright 2022 David Scripka. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# Imports
|
|
import numpy as np
|
|
import os
|
|
import scipy.io.wavfile
|
|
import tempfile
|
|
import openwakeword
|
|
import argparse
|
|
import time
|
|
from speechbrain.dataio.dataio import read_audio
|
|
import collections
|
|
from tqdm import tqdm
|
|
|
|
# Parse input arguments
|
|
parser=argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--input_files",
|
|
help="""A text file where each line is a full path to an audio file to mine for false-positives.""",
|
|
type=str,
|
|
default="./",
|
|
required=True
|
|
)
|
|
parser.add_argument(
|
|
"--skip_files",
|
|
help="""A text file where each line is a full path to an audio file that should be skipped.""",
|
|
type=str,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
"--output_dir",
|
|
help="""Where to save the audio features from a false-positive.
|
|
By default, will be saved as <model_name>.npy files of shape N_clips x frames x features""",
|
|
type=str,
|
|
default="./",
|
|
required=True
|
|
)
|
|
parser.add_argument(
|
|
"--n_threads",
|
|
help="""The number of CPU threads to use when processing.""",
|
|
type=int,
|
|
default=1,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
"--max_wall_time",
|
|
help="""The total amount of wall-clock time (in hours) to mine for false-positives. When this limit is reached
|
|
the examples found up to this point will be saved.""",
|
|
type=float,
|
|
default=1,
|
|
required=False
|
|
)
|
|
parser.add_argument(
|
|
"--max_feature_size",
|
|
help="""The maximum size (in MB) for the false-positive features. If the total collected is larger
|
|
is than this, processing will stop.""",
|
|
type=float,
|
|
default=5000,
|
|
required=False
|
|
)
|
|
args=parser.parse_args()
|
|
|
|
if __name__ == "__main__":
|
|
# Get audio files to mine from input list
|
|
with open(args.input_files, 'r') as f:
|
|
input_files = [i.strip() for i in f.readlines()]
|
|
|
|
# Get audio files to skip and adjust input file list
|
|
if args.skip_files:
|
|
with open(args.skip_files, 'r') as f:
|
|
skip_files = [i.strip() for i in f.readlines()]
|
|
input_files = [i for i in input_files if i not in skip_files]
|
|
|
|
# Set starting time
|
|
start_time = time.time()
|
|
|
|
# Begin processing files
|
|
bs = int(args.n_threads*2)
|
|
combined_features = collections.defaultdict(list)
|
|
for i in tqdm(range(0, len(input_files), bs)):
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
batch = input_files[i:i+bs]
|
|
batch_data = []
|
|
tmp_file_paths = []
|
|
for i in batch:
|
|
dat = read_audio(i).numpy()
|
|
if len(dat.shape) > 1:
|
|
dat = dat[:, 0]
|
|
dat = (dat*32767).astype(np.int16) # convert to 16-khz, 16-bit audio
|
|
|
|
# Save audio to temporary .wav files
|
|
tmp_fname = os.path.join(tmp_dir, i.split(os.path.sep)[-1])
|
|
scipy.io.wavfile.write(tmp_fname, 16000, dat)
|
|
tmp_file_paths.append(tmp_fname)
|
|
|
|
# Predict on temporary files
|
|
predictions = openwakeword.utils.bulk_predict(
|
|
file_paths=tmp_file_paths,
|
|
wakeword_model_paths=[], # loads all default models
|
|
prediction_function="_get_positive_prediction_frames",
|
|
ncpu=args.n_threads
|
|
)
|
|
|
|
# Combine and store features
|
|
for fl in predictions.keys():
|
|
for lbl in predictions[fl].keys():
|
|
combined_features[lbl].append(predictions[fl][lbl])
|
|
|
|
# Check for maximum processing time
|
|
if (time.time() - start_time)/3600 > args.max_wall_time:
|
|
print("\nMaximum wall-time reached. Saving mined false-positives and exiting...")
|
|
break
|
|
|
|
# Check for maximum features size in memory
|
|
size = 0
|
|
for key in combined_features.keys():
|
|
for i in combined_features[key]:
|
|
size += i.nbytes/1e6
|
|
if size > args.max_feature_size:
|
|
print("\nMaximum feature size (in MB) reached. Saving mined false-positives and exiting...")
|
|
break
|
|
|
|
# Combine mined features into single numpy arrays
|
|
for lbl in combined_features.keys():
|
|
combined_features[lbl] = np.concatenate(combined_features[lbl], axis=0)
|
|
|
|
# Save results to .npy files
|
|
if not os.path.exists(args.output_dir):
|
|
os.mkdir(args.output_dir)
|
|
|
|
for key in combined_features.keys():
|
|
np.save(f"{args.output_dir}{os.path.sep}{key}.npy", combined_features[key])
|
|
|