sfx-cues.sh 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. #!/bin/bash
  2. # sfx-cues.sh — 按cue表给无声视频打SFX点(B00阶跃b-roll实战沉淀,2026-07-17)
  3. #
  4. # 用法:bash sfx-cues.sh <无声视频.mp4> <cue表.tsv> <输出.mp4> [--dur=秒]
  5. #
  6. # cue表格式(TSV,#开头为注释):
  7. # 秒数<TAB>sfx相对路径(相对assets/sfx/)<TAB>音量dB
  8. # 例:63.0 impact/brand-stamp.mp3 -13
  9. #
  10. # 音量基准(轻SFX垫口播下):whoosh类-16 / tick类-15 / impact类-12;纯动画成品可整体+4dB
  11. # cue密度参考 audio-design-rules.md 配方(b-roll垫底≈1个/9s,只打结构性节点)
  12. set -e
  13. SFX_DIR="$(cd "$(dirname "$0")/../assets/sfx" && pwd)"
  14. IN="${1:?用法: bash sfx-cues.sh in.mp4 cues.tsv out.mp4 [--dur=210]}"
  15. TABLE="${2:?缺cue表}"
  16. OUT="${3:?缺输出路径}"
  17. DUR=""
  18. for a in "$@"; do case "$a" in --dur=*) DUR="${a#*=}";; esac; done
  19. [ -z "$DUR" ] && DUR=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$IN" | cut -d. -f1)
  20. INPUTS=(-i "$IN")
  21. FILTER=""; MIX=""; i=1
  22. while IFS=$'\t' read -r t f db; do
  23. [ -z "$t" ] && continue
  24. case "$t" in \#*) continue;; esac
  25. [ ! -f "$SFX_DIR/$f" ] && { echo "✗ SFX不存在: $f"; exit 1; }
  26. INPUTS+=(-i "$SFX_DIR/$f")
  27. ms=$(python3 -c "print(int(float('$t')*1000))")
  28. FILTER+="[$i:a]adelay=${ms}:all=1,volume=${db}dB[s$i];"
  29. MIX+="[s$i]"
  30. i=$((i+1))
  31. done < "$TABLE"
  32. N=$((i-1))
  33. [ "$N" = "0" ] && { echo "✗ cue表为空"; exit 1; }
  34. ffmpeg -y -loglevel error "${INPUTS[@]}" \
  35. -filter_complex "${FILTER}${MIX}amix=inputs=${N}:normalize=0,apad=whole_dur=${DUR}[aout]" \
  36. -map 0:v -map "[aout]" -c:v copy -c:a aac -b:a 192k -shortest "$OUT"
  37. echo "✓ ${N}个cue → $OUT"