253 lines
9.8 KiB
Bash
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')"
|