117 lines
3.8 KiB
Bash
Executable file
117 lines
3.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Extract visual and audio snippets from rendered homepage videos.
|
|
#
|
|
# Usage:
|
|
# ./review.sh # recording + recording-mobile
|
|
# ./review.sh recording ad-01-foo # explicit storyboard slugs
|
|
#
|
|
# Outputs land under output/review/current by default. Override REVIEW_DIR
|
|
# if you want to keep multiple passes side by side.
|
|
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")"
|
|
|
|
REVIEW_DIR="${REVIEW_DIR:-output/review/current}"
|
|
mkdir -p "$REVIEW_DIR"
|
|
|
|
if [ "$#" -gt 0 ]; then
|
|
STORYBOARDS=("$@")
|
|
else
|
|
STORYBOARDS=(recording recording-mobile)
|
|
fi
|
|
|
|
for sb in "${STORYBOARDS[@]}"; do
|
|
src="output/$sb/recording.mp4"
|
|
if [ ! -s "$src" ]; then
|
|
echo "[review] missing rendered video: $src" >&2
|
|
exit 1
|
|
fi
|
|
|
|
width="$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 "$src")"
|
|
height="$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "$src")"
|
|
scale=360
|
|
poster_t=16
|
|
if [ "$height" -gt "$width" ]; then
|
|
scale=240
|
|
poster_t=12
|
|
fi
|
|
|
|
ffprobe -v error \
|
|
-select_streams v:0 \
|
|
-show_entries stream=codec_name,width,height,avg_frame_rate \
|
|
-show_entries format=duration,size \
|
|
-of default=noprint_wrappers=1 \
|
|
"$src" > "$REVIEW_DIR/$sb-ffprobe.txt"
|
|
|
|
ffmpeg -nostdin -y -loglevel warning -i "$src" \
|
|
-vf "fps=1/4,scale=${scale}:-1:flags=lanczos,tile=5x3:padding=12:margin=12:color=white" \
|
|
-frames:v 1 -update 1 -q:v 2 "$REVIEW_DIR/$sb-contact.jpg"
|
|
|
|
ffmpeg -nostdin -y -loglevel warning -i "$src" -ss "$poster_t" \
|
|
-frames:v 1 -update 1 -q:v 2 "$REVIEW_DIR/$sb-postercheck-t${poster_t}.jpg"
|
|
|
|
ffmpeg -nostdin -y -loglevel warning -i "$src" -t 12 \
|
|
-vn -ac 1 -ar 24000 "$REVIEW_DIR/$sb-audio-first12.wav"
|
|
done
|
|
|
|
while IFS=$'\t' read -r sb idx clip_start clip_dur midpoint; do
|
|
src="output/$sb/recording.mp4"
|
|
cue="$(printf '%02d' "$idx")"
|
|
|
|
ffmpeg -nostdin -y -loglevel warning -i "$src" -ss "$midpoint" \
|
|
-frames:v 1 -update 1 -q:v 2 "$REVIEW_DIR/$sb-cue-$cue-mid.jpg"
|
|
|
|
ffmpeg -nostdin -y -loglevel warning -ss "$clip_start" -i "$src" -t "$clip_dur" \
|
|
-c:v libx264 -pix_fmt yuv420p -crf 18 -preset veryfast \
|
|
-c:a aac -b:a 128k -movflags +faststart \
|
|
"$REVIEW_DIR/$sb-cue-$cue.mp4"
|
|
|
|
ffmpeg -nostdin -y -loglevel warning -ss "$clip_start" -i "$src" -t "$clip_dur" \
|
|
-vn -ac 1 -ar 24000 "$REVIEW_DIR/$sb-cue-$cue.wav"
|
|
done < <(node - "${STORYBOARDS[@]}" <<'NODE'
|
|
const fs = require('fs');
|
|
const storyboards = process.argv.slice(2);
|
|
const review = process.env.REVIEW_DIR || 'output/review/current';
|
|
|
|
for (const sb of storyboards) {
|
|
const narration = JSON.parse(fs.readFileSync(`output/${sb}/narration.json`, 'utf8'));
|
|
const audioPath = `output/${sb}/audio/index.json`;
|
|
const audio = fs.existsSync(audioPath)
|
|
? JSON.parse(fs.readFileSync(audioPath, 'utf8'))
|
|
: { items: [] };
|
|
const byCue = new Map((audio.items || []).map((item) => [Number(item.cueIndex), item]));
|
|
const rows = ['cueIndex\tstartS\tendS\tdurationS\tgapBeforeMs\twav\ttext'];
|
|
|
|
narration.cues.forEach((cue, i) => {
|
|
const item = byCue.get(i) || {};
|
|
const startMs = Number(cue.videoTimeMs);
|
|
const durationMs = Number(item.durationMs || cue.durationMs);
|
|
const endMs = startMs + durationMs;
|
|
|
|
rows.push([
|
|
i,
|
|
(startMs / 1000).toFixed(3),
|
|
(endMs / 1000).toFixed(3),
|
|
(durationMs / 1000).toFixed(3),
|
|
item.gapBeforeMs ?? '',
|
|
item.wav ?? '',
|
|
cue.text,
|
|
].join('\t'));
|
|
|
|
console.log([
|
|
sb,
|
|
i,
|
|
(Math.max(0, startMs - 250) / 1000).toFixed(3),
|
|
((durationMs + 500) / 1000).toFixed(3),
|
|
((startMs + durationMs / 2) / 1000).toFixed(3),
|
|
].join('\t'));
|
|
});
|
|
|
|
fs.writeFileSync(`${review}/${sb}-timing.tsv`, rows.join('\n') + '\n');
|
|
}
|
|
NODE
|
|
)
|
|
|
|
echo "[review] wrote snippets to $REVIEW_DIR"
|