alright
This commit is contained in:
parent
c645b0f1d4
commit
39ef5c6646
79 changed files with 5660 additions and 2199 deletions
117
video/review.sh
Executable file
117
video/review.sh
Executable file
|
|
@ -0,0 +1,117 @@
|
|||
#!/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"
|
||||
Loading…
Add table
Add a link
Reference in a new issue