Files
blind/audio/process_audio.sh
2026-05-18 18:39:49 +08:00

253 lines
9.8 KiB
Bash

#!/bin/bash
# 《林夏》音频后期批量处理脚本
# 用法: bash audio/process_audio.sh [tts|music|sfx|ambience|all]
# 执行前需要先跑 batch_tts.py 生产原始音频
set -e
cd /home/xsl/blind
TYPE="${1:-all}"
# ──────────────────────────────────────────────────────────────
# TTS 后期处理: 00_raw/tts → 01_processed/tts
# ──────────────────────────────────────────────────────────────
process_tts() {
echo "=== TTS 后期处理 ==="
local count=0
for f in audio/00_raw/tts/lv11_*.wav; do
[ -f "$f" ] || continue
local base=$(basename "$f")
local out="audio/01_processed/tts/$base"
[ -f "$out" ] && echo " 跳过: $base" && continue
# 判断是否为电话/语音形态(需要加听筒效果)
local apply_phone=0
[[ "$base" == *_call_* ]] && apply_phone=1
[[ "$base" == *_voice_* ]] && apply_phone=1
# 例外: mom 第 16 条长语音不加听筒效果
[[ "$base" == *lv11_0200_mom_voice* ]] && apply_phone=0
if [ "$apply_phone" -eq 1 ]; then
# 加听筒效果
ffmpeg -i "$f" \
-af "highpass=f=80,\
acompressor=threshold=-18dB:ratio=4:attack=20:release=250,\
highpass=f=300:poles=2,lowpass=f=3400:poles=2,\
equalizer=f=1500:t=q:w=1:g=1,\
loudnorm=I=-16:TP=-1:LRA=11" \
-ar 48000 -sample_fmt s16 -ac 1 \
-y "$out" 2>/dev/null
else
# 普通处理(文字/系统/长语音)
ffmpeg -i "$f" \
-af "highpass=f=80,\
acompressor=threshold=-18dB:ratio=4:attack=20:release=250,\
loudnorm=I=-16:TP=-1:LRA=11" \
-ar 48000 -sample_fmt s16 -ac 1 \
-y "$out" 2>/dev/null
fi
echo " ✓ $base"
((count++))
done
echo " 处理完成 $count 条"
}
# ──────────────────────────────────────────────────────────────
# 拼接分段长戏
# ──────────────────────────────────────────────────────────────
concat_segments() {
echo "=== 拼接分段长戏 ==="
# 妈妈 4'17" (8 段)
local mom_segs=()
for i in $(seq 1 8); do
local seg="audio/01_processed/tts/lv11_0200_mom_voice_01_seg${i}_v1.wav"
[ -f "$seg" ] && mom_segs+=("$seg")
done
if [ ${#mom_segs[@]} -eq 8 ]; then
printf "file '%s'\n" "${mom_segs[@]}" > /tmp/mom_concat.txt
ffmpeg -f concat -safe 0 -i /tmp/mom_concat.txt \
-c copy -y audio/01_processed/tts/lv11_0200_mom_voice_01_full.wav 2>/dev/null
echo " ✓ 妈妈长语音拼接完成"
else
echo " 跳过妈妈拼接(只有 ${#mom_segs[@]}/8 段)"
fi
# 小美醉语音 (4 段)
local xiaomei_segs=()
for i in $(seq 1 4); do
local seg="audio/01_processed/tts/lv11_2245_xiaomei_voice_01_seg${i}_v1.wav"
[ -f "$seg" ] && xiaomei_segs+=("$seg")
done
if [ ${#xiaomei_segs[@]} -eq 4 ]; then
printf "file '%s'\n" "${xiaomei_segs[@]}" > /tmp/xiaomei_concat.txt
ffmpeg -f concat -safe 0 -i /tmp/xiaomei_concat.txt \
-c copy -y audio/01_processed/tts/lv11_2245_xiaomei_voice_01_full.wav 2>/dev/null
echo " ✓ 小美醉语音拼接完成"
else
echo " 跳过小美拼接(只有 ${#xiaomei_segs[@]}/4 段)"
fi
}
# ──────────────────────────────────────────────────────────────
# 音乐后期: 00_raw/music → 01_processed/music
# ──────────────────────────────────────────────────────────────
process_music() {
echo "=== 音乐后期处理 ==="
local count=0
for f in audio/00_raw/music/*.wav audio/00_raw/music/*.mp3; do
[ -f "$f" ] || continue
local base=$(basename "${f%.*}.wav")
local out="audio/01_processed/music/$base"
[ -f "$out" ] && echo " 跳过: $base" && continue
ffmpeg -i "$f" \
-af "equalizer=f=350:t=q:w=1.5:g=-2,\
loudnorm=I=-18:TP=-1:LRA=11" \
-ar 48000 -ac 2 \
-y "$out" 2>/dev/null
echo " ✓ $base"
((count++))
done
echo " 处理完成 $count 条"
}
# ──────────────────────────────────────────────────────────────
# 音效后期: 00_raw/sfx → 01_processed/sfx
# ──────────────────────────────────────────────────────────────
process_sfx() {
echo "=== 音效后期处理 ==="
local count=0
for f in audio/00_raw/sfx/*.wav audio/00_raw/sfx/*.mp3; do
[ -f "$f" ] || continue
local base=$(basename "${f%.*}.wav")
local out="audio/01_processed/sfx/$base"
[ -f "$out" ] && continue
ffmpeg -i "$f" \
-af "loudnorm=I=-14:TP=-1:LRA=11" \
-ar 48000 \
-y "$out" 2>/dev/null
echo " ✓ $base"
((count++))
done
echo " 处理完成 $count 条"
}
# ──────────────────────────────────────────────────────────────
# 环境底噪后期: 00_raw/ambience → 01_processed/ambience
# ──────────────────────────────────────────────────────────────
process_ambience() {
echo "=== 环境底噪后期处理 ==="
local count=0
for f in audio/00_raw/ambience/*.wav; do
[ -f "$f" ] || continue
local base=$(basename "$f")
local out="audio/01_processed/ambience/$base"
[ -f "$out" ] && continue
local channels=$(ffprobe -v quiet -select_streams a:0 \
-show_entries stream=channels -of csv=p=0 "$f" 2>/dev/null)
if [ "$channels" -eq 1 ] 2>/dev/null; then
# 单声道→假性立体声
ffmpeg -i "$f" \
-filter_complex "[0:a]asplit=2[L][Rc];[Rc]adelay=12|12[R];[L][R]amerge=inputs=2[out]" \
-map "[out]" -ac 2 \
-af "highpass=f=60,lowpass=f=12000,loudnorm=I=-22:TP=-3:LRA=11" \
-ar 48000 -y "$out" 2>/dev/null
else
ffmpeg -i "$f" \
-af "highpass=f=60,lowpass=f=12000,loudnorm=I=-22:TP=-3:LRA=11" \
-ar 48000 -ac 2 -y "$out" 2>/dev/null
fi
echo " ✓ $base"
((count++))
done
echo " 处理完成 $count 条"
}
# ──────────────────────────────────────────────────────────────
# 最终打包: 01_processed → 02_final (.ogg)
# ──────────────────────────────────────────────────────────────
pack_final() {
echo "=== 最终打包 wav → ogg ==="
for subdir in tts music sfx ambience; do
local count=0
for f in audio/01_processed/$subdir/*.wav; do
[ -f "$f" ] || continue
local base=$(basename "${f%.wav}.ogg")
local out="audio/02_final/$subdir/$base"
[ -f "$out" ] && continue
ffmpeg -i "$f" -c:a libvorbis -q:a 5 -y "$out" 2>/dev/null
((count++))
done
[ $count -gt 0 ] && echo " ✓ $subdir: $count 条"
done
echo " 打包完成"
}
# ──────────────────────────────────────────────────────────────
# 响度验收报告
# ──────────────────────────────────────────────────────────────
qa_loudness() {
echo "=== 响度验收 ==="
local pass=0 fail=0
check_dir() {
local dir="$1" target="$2" label="$3"
for f in audio/01_processed/$dir/*.wav; do
[ -f "$f" ] || continue
local lufs=$(ffmpeg -i "$f" -af loudnorm=print_format=json -f null - 2>&1 | \
python3 -c "
import sys, json, re
out = sys.stdin.read()
m = re.search(r'\"input_i\"\s*:\s*\"(-?\d+\.?\d*)\"', out)
print(m.group(1) if m else 'err')
")
local diff=$(echo "$lufs $target" | awk '{d=$1-$2; if(d<0)d=-d; print d}')
local ok=$(echo "$diff" | awk '{print ($1 <= 1.5) ? "pass" : "FAIL"}')
printf " %-50s %s LUFS [%s]\n" "$(basename $f)" "$lufs" "$ok"
[ "$ok" = "pass" ] && ((pass++)) || ((fail++))
done
}
check_dir tts -16 "角色语音"
check_dir music -18 "音乐"
check_dir ambience -22 "环境底噪"
echo ""
echo " 通过: $pass 不通过: $fail"
}
# ──────────────────────────────────────────────────────────────
# 主流程
# ──────────────────────────────────────────────────────────────
echo "《林夏》音频后期处理 $(date '+%Y-%m-%d %H:%M')"
echo "执行类型: $TYPE"
echo ""
case "$TYPE" in
tts) process_tts; concat_segments ;;
music) process_music ;;
sfx) process_sfx ;;
ambience) process_ambience ;;
pack) pack_final ;;
qa) qa_loudness ;;
all)
process_tts
concat_segments
process_music
process_sfx
process_ambience
pack_final
;;
*)
echo "用法: bash audio/process_audio.sh [tts|music|sfx|ambience|pack|qa|all]"
exit 1 ;;
esac
echo ""
echo "完成 $(date '+%H:%M:%S')"