mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 13:03:05 +02:00
fix: getting rnnoise model
This commit is contained in:
parent
06c8121c79
commit
158a1c7b01
104
README_clean_audio.md
Normal file
104
README_clean_audio.md
Normal file
@ -0,0 +1,104 @@
|
||||
# clean_audio.sh — automatic speech cleaning (FFmpeg)
|
||||
|
||||
This script batch‑cleans noisy speech recordings with ffmpeg using simple, reliable filters tuned for ASR (e.g., faster‑whisper). By default it REQUIRES RNNoise (arnndn) and will try to auto‑discover or download a model. You can opt‑in to fallback filters with `--allow-fallback`.
|
||||
|
||||
## Install
|
||||
|
||||
- Required: ffmpeg. Most distros: `sudo pacman -S ffmpeg` or `sudo apt install ffmpeg`.
|
||||
- Recommended: ffmpeg with `arnndn` filter and an RNNoise model file (e.g., from Mozilla RNNoise community models). The script will auto-detect common model locations or download one via `Bash/get_rnnoise_model.sh`. You can pass a specific model with `-m /path/to/model.nn`.
|
||||
|
||||
Make executable:
|
||||
|
||||
```bash
|
||||
chmod +x Bash/clean_audio.sh
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
- Single file, default ASR preset (16k mono, denoise, high‑pass, limiter):
|
||||
```bash
|
||||
Bash/clean_audio.sh path/to/file.wav
|
||||
```
|
||||
This produces `path/to/file_clean.wav`.
|
||||
|
||||
- Whole folder, 4 parallel jobs, output to `cleaned/`:
|
||||
```bash
|
||||
Bash/clean_audio.sh path/to/folder -O cleaned -j 4
|
||||
```
|
||||
|
||||
- Use an RNNoise model explicitly (if your ffmpeg has arnndn):
|
||||
```bash
|
||||
Bash/clean_audio.sh input.wav -m models/rnnoise_model.nn
|
||||
```
|
||||
If you omit `-m`, the script will look in common locations; if not found, it will attempt a download via `Bash/get_rnnoise_model.sh`.
|
||||
|
||||
Advanced options and compatibility:
|
||||
- The cleaner requires RNNoise by default. To allow non-ML fallback filters (afftdn), add `--allow-fallback`.
|
||||
- The script uses advanced filter settings when available (e.g., afftdn with `md`). If your ffmpeg build lacks these options, it will error with guidance. Add `--no-advanced` (or `--compat`) to avoid such params.
|
||||
|
||||
- Podcast preset (adds dynamics and loudness leveling):
|
||||
```bash
|
||||
Bash/clean_audio.sh input.wav --preset podcast
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```text
|
||||
Usage: clean_audio.sh <input-file|input-dir> [options]
|
||||
|
||||
Options:
|
||||
-O, --out-dir DIR Output directory (default: alongside input file).
|
||||
-e, --ext EXT Output extension/container: wav|flac (default: wav).
|
||||
-m, --model PATH RNNoise model file for arnndn; falls back to afftdn if unavailable.
|
||||
--no-ml Do not use arnndn even if model is provided; use afftdn.
|
||||
--preset NAME asr (default) | podcast | aggressive
|
||||
-j, --jobs N Parallel jobs for directory mode (default: 1).
|
||||
-f, --force Overwrite outputs if they exist.
|
||||
-q, --quiet Reduce ffmpeg logging noise.
|
||||
--lowpass FREQ Optional low-pass cutoff (e.g., 8000). Disabled by default.
|
||||
--suffix SUF Suffix for output basename (default: _clean).
|
||||
```
|
||||
|
||||
## Designed for ASR (faster‑whisper)
|
||||
|
||||
Default output format is mono, 16 kHz, PCM 16‑bit WAV—ideal for most Whisper/faster‑whisper pipelines. You can feed the cleaned files directly into your transcription step.
|
||||
|
||||
If you prefer FLAC to save space without quality loss:
|
||||
```bash
|
||||
Bash/clean_audio.sh input.wav -e flac -O cleaned
|
||||
```
|
||||
|
||||
## Presets
|
||||
|
||||
- asr (default): light, ASR‑friendly cleanup; prevents clipping.
|
||||
- podcast: adds gentle dynamics and approximate loudness normalization (single‑pass `loudnorm`).
|
||||
- aggressive: heavier gate/dynamics; can suppress background more, but may slightly hurt ASR accuracy—use sparingly.
|
||||
|
||||
## Tips
|
||||
|
||||
- If you see artifacts from RNNoise, try without a model (uses `afftdn`), or add a low‑pass (e.g., `--lowpass 8000`).
|
||||
- For extremely boomy bar recordings, raise high‑pass by editing `HIGHPASS` in the script or add `--lowpass`.
|
||||
- If your ffmpeg lacks `arnndn`, you can install a newer build or keep the fallback (afftdn works fine for many cases).
|
||||
- If your ffmpeg is missing features, you can use the helper:
|
||||
```bash
|
||||
chmod +x Bash/install_ffmpeg_with_arnndn.sh
|
||||
Bash/install_ffmpeg_with_arnndn.sh
|
||||
```
|
||||
It will suggest distro options or build FFmpeg from source with `--enable-librnnoise`.
|
||||
|
||||
RNNoise model downloader helper:
|
||||
```bash
|
||||
chmod +x Bash/get_rnnoise_model.sh
|
||||
Bash/get_rnnoise_model.sh --yes
|
||||
```
|
||||
This saves a model into `Bash/models/` which the cleaner will auto-discover.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- “arnndn not available”: Your ffmpeg wasn’t built with it. The script will use `afftdn` instead.
|
||||
- Output sounds thin: lower the high‑pass (edit `HIGHPASS=80` in script to `60`) or remove low‑pass.
|
||||
- Level too low/high: choose the `podcast` preset for auto leveling, or add your own `loudnorm` in post.
|
||||
|
||||
## License
|
||||
|
||||
This helper script is provided under the repository’s LICENSE.
|
||||
390
clean_audio.sh
Executable file
390
clean_audio.sh
Executable file
@ -0,0 +1,390 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# clean_audio.sh — Fully automatic audio cleaner for speech (ASR-friendly)
|
||||
#
|
||||
# - Default preset is tuned for ASR (faster-whisper):
|
||||
# mono, 16 kHz, high-pass filter, denoise (RNNoise arnndn by default if model found/provided; else afftdn),
|
||||
# peak limiting to -0.5 dBFS. No aggressive gating/compression by default.
|
||||
# - Optional "podcast" preset adds gentle dynamics and loudness leveling.
|
||||
# - Accepts single files or directories (recursively).
|
||||
# - Optional parallel processing.
|
||||
#
|
||||
# Dependencies: ffmpeg (arnndn filter recommended for best results)
|
||||
# Optional: an RNNoise model file for arnndn (auto-discovered if present; otherwise falls back to afftdn)
|
||||
#
|
||||
# Usage examples:
|
||||
# Bash/clean_audio.sh input.wav # -> input_clean.wav (same folder)
|
||||
# Bash/clean_audio.sh input.wav -O out_dir # -> out_dir/input_clean.wav
|
||||
# Bash/clean_audio.sh input_dir -O cleaned/ -j 4 # -> processes all audio files in dir
|
||||
# Bash/clean_audio.sh input.wav -m models/rn.nn # -> use RNNoise model
|
||||
# Bash/clean_audio.sh input.wav --preset podcast # -> add dynamics leveler
|
||||
#
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
|
||||
|
||||
print_usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 <input-file|input-dir> [options]
|
||||
|
||||
Options:
|
||||
-O, --out-dir DIR Output directory (default: alongside input file).
|
||||
-e, --ext EXT Output extension/container: wav|flac (default: wav).
|
||||
-m, --model PATH RNNoise model file for arnndn; required by default unless --allow-fallback.
|
||||
--no-ml Do not use arnndn even if model is provided (requires --allow-fallback).
|
||||
--preset NAME asr (default) | podcast | aggressive
|
||||
-j, --jobs N Parallel jobs for directory mode (default: 1).
|
||||
-f, --force Overwrite outputs if they exist (ffmpeg -y).
|
||||
-q, --quiet Reduce ffmpeg logging noise.
|
||||
--lowpass FREQ Optional low-pass cutoff (e.g., 8000). Disabled by default.
|
||||
--suffix SUF Suffix for output basename (default: _clean).
|
||||
-h, --help Show this help.
|
||||
|
||||
Notes:
|
||||
- Default sample rate is 16 kHz mono PCM 16-bit (good for most speech ASR models).
|
||||
- If arnndn (RNNoise) is used, it usually outperforms afftdn for speech denoise.
|
||||
- The 'podcast' preset adds gentle dynamics and loudness normalization (single-pass).
|
||||
EOF
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
echo "Error: Required command '$1' not found in PATH" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Defaults
|
||||
OUT_DIR=""
|
||||
OUT_EXT="wav"
|
||||
RN_MODEL=""
|
||||
NO_ML=false
|
||||
REQUIRE_ML=true # default: require RNNoise; install/guide if missing; fail fast if unavailable
|
||||
PRESET="asr"
|
||||
JOBS=1
|
||||
FORCE=false
|
||||
QUIET=false
|
||||
LOWPASS=""
|
||||
SUFFIX="_clean"
|
||||
HIGHPASS="80"
|
||||
AFFTDN_NF="-25" # noise floor in dB for afftdn
|
||||
AFFTDN_MD="8" # mode for afftdn (higher can be more aggressive); requires builds that support 'md'
|
||||
NO_ADVANCED=false # when true, avoid advanced options that some ffmpeg builds lack
|
||||
|
||||
# Parse args
|
||||
if [[ $# -lt 1 ]]; then
|
||||
print_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT_PATH="$1"; shift || true
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-O|--out-dir)
|
||||
OUT_DIR="$2"; shift 2;;
|
||||
-e|--ext)
|
||||
OUT_EXT="$2"; shift 2;;
|
||||
-m|--model)
|
||||
RN_MODEL="$2"; shift 2;;
|
||||
--no-ml)
|
||||
NO_ML=true; shift;;
|
||||
--preset)
|
||||
PRESET="$2"; shift 2;;
|
||||
-j|--jobs)
|
||||
JOBS="$2"; shift 2;;
|
||||
-f|--force)
|
||||
FORCE=true; shift;;
|
||||
-q|--quiet)
|
||||
QUIET=true; shift;;
|
||||
--lowpass)
|
||||
LOWPASS="$2"; shift 2;;
|
||||
--suffix)
|
||||
SUFFIX="$2"; shift 2;;
|
||||
--no-advanced|--compat)
|
||||
NO_ADVANCED=true; shift;;
|
||||
--allow-fallback)
|
||||
REQUIRE_ML=false; shift;;
|
||||
-h|--help)
|
||||
print_usage; exit 0;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
print_usage
|
||||
exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_cmd ffmpeg
|
||||
|
||||
# Resolve FFmpeg binary (env override -> local build -> system)
|
||||
FFMPEG_BIN=${FFMPEG_BIN:-}
|
||||
if [[ -z "${FFMPEG_BIN}" ]]; then
|
||||
if [[ -x "$SCRIPT_DIR/ffmpeg-build/FFmpeg/ffmpeg" ]]; then
|
||||
FFMPEG_BIN="$SCRIPT_DIR/ffmpeg-build/FFmpeg/ffmpeg"
|
||||
else
|
||||
FFMPEG_BIN="ffmpeg"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! command -v "$FFMPEG_BIN" >/dev/null 2>&1 && [[ ! -x "$FFMPEG_BIN" ]]; then
|
||||
echo "Error: FFmpeg binary not found: $FFMPEG_BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! $QUIET; then
|
||||
echo "Using FFmpeg binary: $FFMPEG_BIN" >&2
|
||||
fi
|
||||
|
||||
FFMPEG_LOG=(-hide_banner)
|
||||
if $QUIET; then
|
||||
FFMPEG_LOG+=( -loglevel error )
|
||||
else
|
||||
FFMPEG_LOG+=( -loglevel info )
|
||||
fi
|
||||
|
||||
FFMPEG_OVERWRITE=(-n)
|
||||
if $FORCE; then
|
||||
FFMPEG_OVERWRITE=(-y)
|
||||
fi
|
||||
|
||||
arnndn_available=false
|
||||
if "$FFMPEG_BIN" -hide_banner -h filter=arnndn >/dev/null 2>&1; then
|
||||
arnndn_available=true
|
||||
else
|
||||
if "$FFMPEG_BIN" -hide_banner -filters 2>/dev/null | grep -Eq '(^|[[:space:]])arnndn([[:space:]]|$)'; then
|
||||
arnndn_available=true
|
||||
fi
|
||||
fi
|
||||
if ! $QUIET; then
|
||||
echo "arnndn_available=$arnndn_available" >&2
|
||||
fi
|
||||
|
||||
# Check if afftdn supports 'md' option
|
||||
afftdn_supports_md=false
|
||||
if "$FFMPEG_BIN" -hide_banner -h filter=afftdn 2>/dev/null | grep -q " md="; then
|
||||
afftdn_supports_md=true
|
||||
fi
|
||||
|
||||
# Try to auto-discover an RNNoise model if none provided
|
||||
find_default_rn_model() {
|
||||
local candidate=""
|
||||
# Allow env variable override
|
||||
if [[ -n "${RNNOISE_MODEL:-}" && -f "${RNNOISE_MODEL}" ]]; then
|
||||
echo "${RNNOISE_MODEL}"
|
||||
return 0
|
||||
fi
|
||||
local dirs=(
|
||||
"$SCRIPT_DIR/models"
|
||||
"$SCRIPT_DIR/../models"
|
||||
"/usr/share/rnnoise"
|
||||
"/usr/local/share/rnnoise"
|
||||
"/usr/share/ffmpeg/models"
|
||||
"$HOME/.local/share/rnnoise"
|
||||
)
|
||||
# Prefer '.rnnn' models (rnnoise-nu style) over legacy '.nn'
|
||||
local exts=("rnnn" "nn" "model")
|
||||
for d in "${dirs[@]}"; do
|
||||
if [[ -d "$d" ]]; then
|
||||
for ext in "${exts[@]}"; do
|
||||
# Pick the first matching model file
|
||||
for f in "$d"/*."$ext"; do
|
||||
if [[ -f "$f" ]]; then
|
||||
echo "$f"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
use_arnndn=false
|
||||
if [[ $NO_ML == false ]]; then
|
||||
if [[ $arnndn_available == false ]]; then
|
||||
if $REQUIRE_ML; then
|
||||
echo "Error: FFmpeg 'arnndn' filter not available. Please install/upgrade FFmpeg with librnnoise (see Bash/install_ffmpeg_with_arnndn.sh)." >&2
|
||||
exit 9
|
||||
fi
|
||||
else
|
||||
# arnndn available; require an external model
|
||||
if [[ -n "$RN_MODEL" && -f "$RN_MODEL" ]]; then
|
||||
:
|
||||
else
|
||||
if model_path=$(find_default_rn_model); then
|
||||
RN_MODEL="$model_path"
|
||||
else
|
||||
if [[ -x "$SCRIPT_DIR/get_rnnoise_model.sh" ]]; then
|
||||
RN_TARGET_DIR="$SCRIPT_DIR/models" RN_TARGET_NAME="rnnoise_model.rnnn" "$SCRIPT_DIR/get_rnnoise_model.sh" --yes || true
|
||||
if model_path=$(find_default_rn_model); then
|
||||
RN_MODEL="$model_path"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [[ -z "$RN_MODEL" ]]; then
|
||||
echo "Error: RNNoise model required but not found. Automatic download failed." >&2
|
||||
echo "Hint: Set RN_URL to a reachable model URL and run Bash/get_rnnoise_model.sh, or supply -m /path/to/model.nn." >&2
|
||||
exit 10
|
||||
fi
|
||||
use_arnndn=true
|
||||
echo "Using RNNoise external model: $RN_MODEL" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
build_filters() {
|
||||
local filters=()
|
||||
# Remove low-frequency rumble typical for handheld/room noise
|
||||
filters+=("highpass=f=${HIGHPASS}")
|
||||
|
||||
# Denoise
|
||||
if $use_arnndn; then
|
||||
# arnndn with full mix keeps the model output; if no external model, rely on built-in
|
||||
filters+=("aresample=48000")
|
||||
filters+=("arnndn=m=${RN_MODEL}:mix=1.0")
|
||||
else
|
||||
# afftdn: FFT-based denoise, tune nf (noise floor) as needed
|
||||
if $REQUIRE_ML; then
|
||||
echo "Error: RNNoise required but not in use; aborting rather than falling back to afftdn. Use --allow-fallback to permit." >&2
|
||||
exit 11
|
||||
fi
|
||||
if $NO_ADVANCED; then
|
||||
filters+=("afftdn=nf=${AFFTDN_NF}")
|
||||
else
|
||||
if $afftdn_supports_md; then
|
||||
filters+=("afftdn=nf=${AFFTDN_NF}:md=${AFFTDN_MD}")
|
||||
else
|
||||
echo "Error: Your ffmpeg's afftdn filter does not support 'md='." >&2
|
||||
echo "Hint: Install/upgrade ffmpeg to a build that supports afftdn md or rerun with --no-advanced." >&2
|
||||
echo " On Debian/Ubuntu you may need a newer ffmpeg from a PPA or build from source." >&2
|
||||
exit 8
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Optional low-pass to shave hiss; keep disabled unless requested
|
||||
if [[ -n "$LOWPASS" ]]; then
|
||||
filters+=("lowpass=f=${LOWPASS}")
|
||||
fi
|
||||
|
||||
case "$PRESET" in
|
||||
asr)
|
||||
# ASR-friendly: avoid heavy gating/leveling, just prevent clipping
|
||||
filters+=("alimiter=limit=0.94")
|
||||
;;
|
||||
podcast)
|
||||
# Gentle dynamic normalization and broadcast-ish loudness (single-pass)
|
||||
# Note: single-pass loudnorm is approximate but OK for quick workflows
|
||||
filters+=("dynaudnorm=f=500:g=5:p=0.1")
|
||||
filters+=("loudnorm=i=-18:lra=9:tp=-2.0")
|
||||
;;
|
||||
aggressive)
|
||||
# Heavier clean-up; may harm ASR slightly but suppress background more
|
||||
filters+=("agate=threshold=0.012:ratio=2.5:release=200")
|
||||
filters+=("dynaudnorm=f=400:g=7:p=0.1")
|
||||
filters+=("loudnorm=i=-18:lra=9:tp=-2.0")
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
# Resample and format at the end for ASR
|
||||
filters+=("aresample=16000")
|
||||
filters+=("aformat=channel_layouts=mono:sample_fmts=s16")
|
||||
|
||||
local IFS=","; echo "${filters[*]}"
|
||||
}
|
||||
|
||||
make_out_path_for_file() {
|
||||
local in_file="$1"
|
||||
local base
|
||||
base=$(basename -- "$in_file")
|
||||
base="${base%.*}"
|
||||
local out_base="${base}${SUFFIX}.${OUT_EXT}"
|
||||
if [[ -n "$OUT_DIR" ]]; then
|
||||
mkdir -p -- "$OUT_DIR"
|
||||
echo "$OUT_DIR/$out_base"
|
||||
else
|
||||
local dir
|
||||
dir=$(dirname -- "$in_file")
|
||||
echo "$dir/$out_base"
|
||||
fi
|
||||
}
|
||||
|
||||
process_one() {
|
||||
local in_file="$1"
|
||||
local out_file
|
||||
out_file=$(make_out_path_for_file "$in_file")
|
||||
|
||||
# Choose codec based on extension
|
||||
local codec=( -c:a pcm_s16le )
|
||||
if [[ "$OUT_EXT" == "flac" ]]; then
|
||||
codec=( -c:a flac )
|
||||
fi
|
||||
|
||||
local af
|
||||
af=$(build_filters)
|
||||
|
||||
if [[ -f "$out_file" && $FORCE == false ]]; then
|
||||
echo "Skip (exists): $out_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Cleaning: $in_file -> $out_file"
|
||||
"$FFMPEG_BIN" "${FFMPEG_LOG[@]}" "${FFMPEG_OVERWRITE[@]}" -i "$in_file" -af "$af" "${codec[@]}" "$out_file"
|
||||
}
|
||||
|
||||
# Concurrency helpers (bash >= 5 supports wait -n; fallback to sequential if not)
|
||||
supports_wait_n=false
|
||||
if [[ -n "${BASH_VERSINFO:-}" && ${BASH_VERSINFO[0]} -ge 5 ]]; then
|
||||
supports_wait_n=true
|
||||
fi
|
||||
|
||||
run_dir() {
|
||||
local dir="$1"
|
||||
# Common audio extensions (case-insensitive)
|
||||
mapfile -d '' files < <(find "$dir" -type f \
|
||||
\( -iname "*.wav" -o -iname "*.mp3" -o -iname "*.m4a" -o -iname "*.aac" -o -iname "*.flac" \
|
||||
-o -iname "*.ogg" -o -iname "*.opus" -o -iname "*.wma" -o -iname "*.webm" \) -print0)
|
||||
|
||||
if [[ ${#files[@]} -eq 0 ]]; then
|
||||
echo "No audio files found in: $dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local running=0
|
||||
for f in "${files[@]}"; do
|
||||
if [[ "$JOBS" -le 1 || $supports_wait_n == false ]]; then
|
||||
process_one "$f"
|
||||
else
|
||||
process_one "$f" &
|
||||
((running++))
|
||||
if (( running >= JOBS )); then
|
||||
wait -n || true
|
||||
((running--))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Wait for any remaining background jobs
|
||||
if (( JOBS > 1 )) && $supports_wait_n; then
|
||||
wait || true
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
# Sanity checks and notices
|
||||
if [[ -n "$RN_MODEL" && $use_arnndn == false && $NO_ML == false ]]; then
|
||||
echo "Note: arnndn filter not available in your ffmpeg or model missing — using afftdn." >&2
|
||||
fi
|
||||
|
||||
if [[ -f "$INPUT_PATH" ]]; then
|
||||
process_one "$INPUT_PATH"
|
||||
elif [[ -d "$INPUT_PATH" ]]; then
|
||||
run_dir "$INPUT_PATH"
|
||||
else
|
||||
echo "Error: Input path not found: $INPUT_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
190
get_rnnoise_model.sh
Executable file
190
get_rnnoise_model.sh
Executable file
@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# get_rnnoise_model.sh — fetch an RNNoise model into a local models dir
|
||||
#
|
||||
# Prefers known-good rnnoise-nu models. You can override with:
|
||||
# RN_URL, RN_TARGET_DIR, RN_TARGET_NAME
|
||||
#
|
||||
# Usage:
|
||||
# Bash/get_rnnoise_model.sh # interactive download
|
||||
# RN_TARGET_DIR=./models Bash/get_rnnoise_model.sh --yes
|
||||
|
||||
ask_yes_no() {
|
||||
read -r -p "$1 [y/N]: " ans || true
|
||||
case "${ans:-}" in
|
||||
y|Y|yes|YES) return 0;;
|
||||
*) return 1;;
|
||||
esac
|
||||
}
|
||||
|
||||
has_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
YES=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-y|--yes) YES=true; shift;;
|
||||
*) echo "Unknown option: $1" >&2; exit 2;;
|
||||
esac
|
||||
done
|
||||
|
||||
RN_TARGET_DIR=${RN_TARGET_DIR:-"$(dirname "$0")/models"}
|
||||
RN_TARGET_NAME=${RN_TARGET_NAME:-"rnnoise_model.rnnn"}
|
||||
|
||||
mkdir -p "$RN_TARGET_DIR"
|
||||
dest="$RN_TARGET_DIR/$RN_TARGET_NAME"
|
||||
|
||||
if [[ -f "$dest" ]]; then
|
||||
echo "Model already exists at: $dest"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! $YES; then
|
||||
if ! ask_yes_no "Download RNNoise model to $dest?"; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! has_cmd curl && ! has_cmd wget; then
|
||||
echo "Error: Need curl or wget to download RNNoise model." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# Priority 1: explicit URL
|
||||
if [[ -n "${RN_URL:-}" ]]; then
|
||||
echo "Downloading RNNoise model from RN_URL: $RN_URL" >&2
|
||||
tmp=$(mktemp)
|
||||
if has_cmd curl; then
|
||||
curl -fsSL "$RN_URL" -o "$tmp"
|
||||
else
|
||||
wget -qO "$tmp" "$RN_URL"
|
||||
fi
|
||||
if [[ -s "$tmp" ]]; then
|
||||
mv "$tmp" "$dest"
|
||||
echo "Saved RNNoise model to: $dest"
|
||||
exit 0
|
||||
fi
|
||||
rm -f "$tmp" || true
|
||||
echo "Warning: RN_URL download failed; continuing to fallback sources." >&2
|
||||
fi
|
||||
|
||||
# Priority 2: rnnoise-nu known models (GregorR)
|
||||
NU_URLS=(
|
||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/sh.rnnn"
|
||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/lq.rnnn"
|
||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/mp.rnnn"
|
||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/bd.rnnn"
|
||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/cb.rnnn"
|
||||
)
|
||||
for u in "${NU_URLS[@]}"; do
|
||||
echo "Attempting to download RNNoise model from: $u" >&2
|
||||
tmp=$(mktemp)
|
||||
if has_cmd curl; then
|
||||
if curl -fsSL "$u" -o "$tmp"; then
|
||||
if [[ -s "$tmp" ]]; then
|
||||
mv "$tmp" "$dest"
|
||||
echo "Saved RNNoise model to: $dest" >&2
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if wget -qO "$tmp" "$u"; then
|
||||
if [[ -s "$tmp" ]]; then
|
||||
mv "$tmp" "$dest"
|
||||
echo "Saved RNNoise model to: $dest" >&2
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
rm -f "$tmp" || true
|
||||
done
|
||||
|
||||
# Priority 2b: arnndn-models fallback (richardpl)
|
||||
RNNDN_URLS=(
|
||||
"https://raw.githubusercontent.com/richardpl/arnndn-models/master/sh.rnnn"
|
||||
)
|
||||
for u in "${RNNDN_URLS[@]}"; do
|
||||
echo "Attempting to download RNNoise model from: $u" >&2
|
||||
tmp=$(mktemp)
|
||||
if has_cmd curl; then
|
||||
if curl -fsSL "$u" -o "$tmp"; then
|
||||
if [[ -s "$tmp" ]]; then
|
||||
mv "$tmp" "$dest"
|
||||
echo "Saved RNNoise model to: $dest" >&2
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if wget -qO "$tmp" "$u"; then
|
||||
if [[ -s "$tmp" ]]; then
|
||||
mv "$tmp" "$dest"
|
||||
echo "Saved RNNoise model to: $dest" >&2
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
rm -f "$tmp" || true
|
||||
done
|
||||
|
||||
# Priority 3: repo archives (rnnoise-nu and arnndn-models)
|
||||
ARCHIVES=(
|
||||
"https://github.com/GregorR/rnnoise-nu/archive/refs/heads/master.zip"
|
||||
"https://github.com/richardpl/arnndn-models/archive/refs/heads/master.zip"
|
||||
)
|
||||
for aurl in "${ARCHIVES[@]}"; do
|
||||
echo "Attempting to download archive: $aurl" >&2
|
||||
tmpdir=$(mktemp -d)
|
||||
archive="$tmpdir/models.zip"
|
||||
set +e
|
||||
if has_cmd curl; then
|
||||
curl -fL "$aurl" -o "$archive"
|
||||
else
|
||||
wget -O "$archive" "$aurl"
|
||||
fi
|
||||
status=$?
|
||||
set -e
|
||||
if [[ $status -ne 0 ]]; then
|
||||
rm -rf "$tmpdir" || true
|
||||
continue
|
||||
fi
|
||||
if has_cmd bsdtar; then
|
||||
bsdtar -xf "$archive" -C "$tmpdir"
|
||||
elif has_cmd unzip; then
|
||||
unzip -q "$archive" -d "$tmpdir"
|
||||
else
|
||||
echo "Warning: Need bsdtar or unzip to extract archive; skipping archive method." >&2
|
||||
rm -rf "$tmpdir" || true
|
||||
continue
|
||||
fi
|
||||
mapfile -t nnfiles < <(bash -lc 'shopt -s globstar nullglob; for f in '"$tmpdir"'/**/*.rnnn '"$tmpdir"'/**/*.nn; do [[ -f "$f" ]] && echo "$f"; done')
|
||||
if [[ ${#nnfiles[@]} -gt 0 ]]; then
|
||||
cp -f "${nnfiles[0]}" "$dest"
|
||||
echo "Saved RNNoise model to: $dest (from archive)" >&2
|
||||
rm -rf "$tmpdir" || true
|
||||
exit 0
|
||||
fi
|
||||
rm -rf "$tmpdir" || true
|
||||
done
|
||||
|
||||
# Priority 4: Arch-based AUR packages and search only .nn/.rnnn
|
||||
if has_cmd yay; then
|
||||
echo "Attempting to install AUR packages that may include RNNoise models..." >&2
|
||||
set +e
|
||||
yay -S --noconfirm denoiseit-git 2>/dev/null
|
||||
yay -S --noconfirm speech-denoiser-git 2>/dev/null
|
||||
set -e
|
||||
mapfile -t found < <(bash -lc 'shopt -s globstar nullglob; for f in /usr/share/**/*.nn /usr/share/**/*.rnnn /usr/local/share/**/*.nn /usr/local/share/**/*.rnnn; do [[ -f "$f" ]] && echo "$f"; done' 2>/dev/null || true)
|
||||
if [[ ${#found[@]} -gt 0 ]]; then
|
||||
echo "Found candidate models:" >&2
|
||||
printf ' %s\n' "${found[@]}" >&2
|
||||
cp -f "${found[0]}" "$dest"
|
||||
echo "Copied model to: $dest" >&2
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Error: Could not obtain an RNNoise model automatically." >&2
|
||||
echo "Hint: Set RN_URL to a reachable model URL, or place a model file at: $dest" >&2
|
||||
exit 5
|
||||
130
install_ffmpeg_with_arnndn.sh
Executable file
130
install_ffmpeg_with_arnndn.sh
Executable file
@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# install_ffmpeg_with_arnndn.sh — helper to install/upgrade FFmpeg with arnndn and full audio filters
|
||||
#
|
||||
# Tries distro packages first; if not suitable, offers to build from source.
|
||||
# This script prints commands and asks for confirmation before building.
|
||||
|
||||
print_info() {
|
||||
echo "[info] $*"
|
||||
}
|
||||
|
||||
ask_yes_no() {
|
||||
read -r -p "$1 [y/N]: " ans || true
|
||||
case "${ans:-}" in
|
||||
y|Y|yes|YES) return 0;;
|
||||
*) return 1;;
|
||||
esac
|
||||
}
|
||||
|
||||
has_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
detect_distro() {
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
. /etc/os-release
|
||||
echo "${ID:-unknown}"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local distro
|
||||
distro=$(detect_distro)
|
||||
print_info "Detected distro: $distro"
|
||||
|
||||
if has_cmd ffmpeg && ffmpeg -hide_banner -filters | grep -q " arnndn "; then
|
||||
print_info "Your ffmpeg already supports arnndn."
|
||||
else
|
||||
case "$distro" in
|
||||
ubuntu|debian)
|
||||
print_info "On Ubuntu/Debian, the official repo may lack newer filters. Consider a PPA or build from source."
|
||||
echo "Options:"
|
||||
echo " - ppa: sudo add-apt-repository ppa:savoury1/ffmpeg6 && sudo apt update && sudo apt install ffmpeg"
|
||||
echo " - source build (recommended for latest): run this script to build from source"
|
||||
;;
|
||||
arch|manjaro|endeavouros)
|
||||
print_info "On Arch-based distros, ffmpeg is recent. Try: sudo pacman -Syu ffmpeg"
|
||||
;;
|
||||
fedora)
|
||||
print_info "On Fedora, try: sudo dnf install ffmpeg"
|
||||
;;
|
||||
*)
|
||||
print_info "Distro not recognized; will offer source build."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if ask_yes_no "Build FFmpeg from source with rnnoise/arnndn support now?"; then
|
||||
echo "This will clone FFmpeg and build locally under ./ffmpeg-build. Continue?"
|
||||
if ! ask_yes_no "Proceed"; then
|
||||
exit 0
|
||||
fi
|
||||
set -x
|
||||
mkdir -p ffmpeg-build && cd ffmpeg-build
|
||||
# Prepare repository
|
||||
if [[ -d FFmpeg ]]; then
|
||||
if [[ -d FFmpeg/.git ]]; then
|
||||
if ask_yes_no "An existing FFmpeg source directory was found. Reuse and update it?"; then
|
||||
set +e
|
||||
git -C FFmpeg fetch --all --tags --prune
|
||||
git -C FFmpeg pull --rebase --ff-only || true
|
||||
set -e
|
||||
else
|
||||
if ask_yes_no "Delete existing FFmpeg directory and re-clone?"; then
|
||||
rm -rf FFmpeg
|
||||
else
|
||||
echo "Keeping existing FFmpeg directory as-is."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if ask_yes_no "Non-git 'FFmpeg' directory exists. Delete and re-clone?"; then
|
||||
rm -rf FFmpeg
|
||||
else
|
||||
echo "Cannot proceed with a non-git FFmpeg directory present. Aborting."
|
||||
exit 4
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
# Dependencies
|
||||
if [[ "$distro" == "ubuntu" || "$distro" == "debian" ]]; then
|
||||
sudo apt update
|
||||
sudo apt install -y git build-essential yasm nasm pkg-config libx264-dev libx265-dev libvpx-dev libopus-dev libfdk-aac-dev libmp3lame-dev libvorbis-dev libass-dev libfreetype6-dev libgnutls28-dev libaom-dev libdav1d-dev libxvidcore-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libxcb-shape0-dev libdrm-dev libvulkan-dev libva-dev libvdpau-dev librtmp-dev libunistring-dev libgnutls28-dev libchromaprint-dev libbluray-dev librubberband-dev libspeex-dev libsoxr-dev libvmaf-dev libzimg-dev libsvtav1-dev libtheora-dev libwebp-dev libopenal-dev libjack-jackd2-dev libpulse-dev librnnoise-dev
|
||||
elif [[ "$distro" == "arch" || "$distro" == "manjaro" || "$distro" == "endeavouros" ]]; then
|
||||
sudo pacman -Syu --needed base-devel yasm nasm pkgconf rnnoise
|
||||
elif [[ "$distro" == "fedora" ]]; then
|
||||
sudo dnf install -y git make gcc yasm nasm pkgconf-pkg-config rnnoise-devel libX11-devel libXext-devel libXfixes-devel libXv-devel libXrandr-devel libXi-devel libXtst-devel libXinerama-devel freetype-devel fontconfig-devel libass-devel libvpx-devel libaom-devel libdav1d-devel zimg-devel rubberband-devel soxr-devel libvorbis-devel opus-devel lame-devel
|
||||
else
|
||||
echo "Note: please ensure rnnoise development headers are installed (pkg-config rnnoise)." >&2
|
||||
fi
|
||||
if [[ ! -d FFmpeg/.git ]]; then
|
||||
git clone https://github.com/FFmpeg/FFmpeg.git --depth=1
|
||||
fi
|
||||
cd FFmpeg
|
||||
RN_FLAG=""
|
||||
# Some FFmpeg versions auto-detect rnnoise without a flag; include the flag only if supported
|
||||
if ./configure --help | grep -q "librnnoise"; then
|
||||
RN_FLAG="--enable-librnnoise"
|
||||
else
|
||||
echo "[info] configure has no --enable-librnnoise; relying on auto-detection via pkg-config (rnnoise)." >&2
|
||||
fi
|
||||
|
||||
./configure \
|
||||
--enable-gpl --enable-nonfree \
|
||||
--enable-libx264 --enable-libx265 --enable-libvpx --enable-libopus --enable-libmp3lame \
|
||||
--enable-libvorbis --enable-libass --enable-fontconfig --enable-libfreetype \
|
||||
--enable-librubberband --enable-libsoxr --enable-libzimg --enable-libvmaf \
|
||||
--enable-libdav1d --enable-libaom --enable-libsvtav1 \
|
||||
${RN_FLAG} \
|
||||
--enable-ffplay --enable-ffprobe
|
||||
make -j"$(nproc)"
|
||||
echo "Build complete. You can run ./ffmpeg-build/FFmpeg/ffmpeg from this folder or 'sudo make install' to install system-wide."
|
||||
set +x
|
||||
else
|
||||
echo "Skipped building from source."
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue
Block a user