Files
BZJZ_Material/文档润色流和知识库构建流/huashu-design/scripts/render-narration.sh

152 lines
5.3 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# render-narration.sh · 一条龙HTML 解说动画 → 最终 MP4带人声
#
# 流水线:
# 1. render-video.js 录无声 MP4按 timeline.totalDuration
# 2. mix-voiceover.sh 混入 voiceover.mp3可选 BGM
# 3. 输出 <basename>-narrated.mp4
#
# Usage:
# bash render-narration.sh <html> --timeline=<path> [options]
#
# Required:
# <html> 解说动画的 HTML应内嵌 NarrationStage + recording 模式 rAF 自驱)
# --timeline=<path> timeline.json 路径(自动读 totalDuration 和 voiceover.mp3 路径)
#
# Optional:
# --bgm-mood=<name> BGM 预设educational / tech / tutorial / ...
# --bgm=<path> 自定义 BGM 文件
# --bgm-volume=<0-1> BGM 静态音量,默认 0.18
# --no-ducking 关 sidechain ducking
# --keep-silent 保留中间产物(无声 MP4便于 debug
# --seek 用 render-video-seek.js 逐帧 seek 渲染(真 60fps·确定性·无黑帧
# --seek-fps=<n> seek 渲染帧率,默认 60需配合 --seek
# --out=<path> 输出路径,默认 <html-basename>-narrated.mp4
# --width=<px> 视频宽度(默认 1920
# --height=<px> 视频高度(默认 1080
#
# Examples:
# bash render-narration.sh demo.html --timeline=_narration/timeline.json
# bash render-narration.sh demo.html --timeline=_narration/timeline.json --bgm-mood=educational
#
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$SCRIPT_DIR/.."
HTML=""
TIMELINE=""
BGM_MOOD=""
BGM=""
BGM_VOLUME="0.18"
NO_DUCKING=""
KEEP_SILENT=""
USE_SEEK=""
SEEK_FPS="60"
OUT=""
WIDTH="1920"
HEIGHT="1080"
for arg in "$@"; do
case "$arg" in
--timeline=*) TIMELINE="${arg#*=}" ;;
--bgm-mood=*) BGM_MOOD="${arg#*=}" ;;
--bgm=*) BGM="${arg#*=}" ;;
--bgm-volume=*) BGM_VOLUME="${arg#*=}" ;;
--no-ducking) NO_DUCKING="--no-ducking" ;;
--keep-silent) KEEP_SILENT="1" ;;
--seek) USE_SEEK="1" ;;
--seek-fps=*) SEEK_FPS="${arg#*=}" ;;
--out=*) OUT="${arg#*=}" ;;
--width=*) WIDTH="${arg#*=}" ;;
--height=*) HEIGHT="${arg#*=}" ;;
-*) echo "未知参数:$arg" >&2; exit 1 ;;
*) HTML="$arg" ;;
esac
done
if [ -z "$HTML" ] || [ ! -f "$HTML" ]; then
echo "Usage: bash render-narration.sh <html> --timeline=<path> [options]" >&2
exit 1
fi
if [ -z "$TIMELINE" ] || [ ! -f "$TIMELINE" ]; then
echo "✗ 缺 --timeline=<path>timeline.json 由 narrate-pipeline.mjs 生成)" >&2
exit 1
fi
# ── 从 timeline.json 读 totalDuration 和 voiceover 路径 ──
TIMELINE_DIR="$(cd "$(dirname "$TIMELINE")" && pwd)"
TOTAL_DURATION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$TIMELINE','utf8')).totalDuration)")
VOICEOVER_REL=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$TIMELINE','utf8')).voiceover || 'voiceover.mp3')")
VOICEOVER="$TIMELINE_DIR/$VOICEOVER_REL"
if [ ! -f "$VOICEOVER" ]; then
echo "✗ voiceover.mp3 不存在: $VOICEOVER" >&2
exit 1
fi
# 录制时长 = 总时长 + 1s 安全缓冲
RECORD_DURATION=$(node -e "console.log(Math.ceil($TOTAL_DURATION + 1))")
HTML_ABS="$(cd "$(dirname "$HTML")" && pwd)/$(basename "$HTML")"
HTML_DIR="$(dirname "$HTML_ABS")"
HTML_BASE="$(basename "$HTML" .html)"
SILENT_MP4="$HTML_DIR/$HTML_BASE.mp4"
if [ -z "$OUT" ]; then
OUT="$HTML_DIR/$HTML_BASE-narrated.mp4"
fi
echo "═══ render-narration ═══════════════════"
echo " HTML: $HTML_ABS"
echo " Timeline: $TIMELINE"
echo " Voiceover: $VOICEOVER"
echo " Total dur: ${TOTAL_DURATION}s (录 ${RECORD_DURATION}s)"
echo " 尺寸: ${WIDTH}×${HEIGHT}"
[ -n "$BGM_MOOD" ] && echo " BGM mood: $BGM_MOOD"
[ -n "$BGM" ] && echo " BGM: $BGM"
echo " 最终输出: $OUT"
echo "════════════════════════════════════════"
# ── Step 1: 录无声 MP4 ──────────────────────
echo ""
if [ -n "$USE_SEEK" ]; then
echo "▸ Step 1/2 · 逐帧 seek 渲染 HTML 动画 (无声 · ${SEEK_FPS}fps 确定性)"
NODE_PATH=$(npm root -g) node "$SCRIPT_DIR/render-video-seek.js" "$HTML_ABS" \
--duration="$RECORD_DURATION" \
--fps="$SEEK_FPS" \
--width="$WIDTH" \
--height="$HEIGHT"
else
echo "▸ Step 1/2 · 录制 HTML 动画 (无声)"
NODE_PATH=$(npm root -g) node "$SCRIPT_DIR/render-video.js" "$HTML_ABS" \
--duration="$RECORD_DURATION" \
--width="$WIDTH" \
--height="$HEIGHT"
fi
if [ ! -f "$SILENT_MP4" ]; then
echo "✗ 无声 MP4 没生成: $SILENT_MP4" >&2
exit 1
fi
# ── Step 2: 混入人声 ──────────────────────
echo ""
echo "▸ Step 2/2 · 混入人声"
MIX_ARGS=("$SILENT_MP4" "--voiceover=$VOICEOVER" "--out=$OUT")
[ -n "$BGM_MOOD" ] && MIX_ARGS+=("--bgm-mood=$BGM_MOOD")
[ -n "$BGM" ] && MIX_ARGS+=("--bgm=$BGM")
[ -n "$BGM_MOOD$BGM" ] && MIX_ARGS+=("--bgm-volume=$BGM_VOLUME")
[ -n "$NO_DUCKING" ] && MIX_ARGS+=("$NO_DUCKING")
bash "$SCRIPT_DIR/mix-voiceover.sh" "${MIX_ARGS[@]}"
# 清理中间产物
if [ -z "$KEEP_SILENT" ]; then
rm -f "$SILENT_MP4"
fi
echo ""
echo "✓ 完成: $OUT"
[ -n "$KEEP_SILENT" ] && echo " (中间产物保留: $SILENT_MP4)"