Zum Inhalt

scripts/build-topshelf-hls.sh

#!/usr/bin/env bash
# Builds an Apple-HLS-Authoring-Spec-compliant VOD stream for the tvOS Top Shelf preview video,
# from public/topshelf/hero.mp4:
#   - re-encodes H.264 High@4.0, 1080p30, regular GOP (keyframe every 2s) so segments are clean
#   - adds a silent AAC-LC stereo track (Apple's spec/validator wants audio; the preview plays muted)
#   - 6s VOD segments, independent_segments
#   - writes a MULTIVARIANT (master) playlist master.m3u8 -> stream.m3u8 with BANDWIDTH/CODECS/RESOLUTION
# Output: public/topshelf/hls/{master.m3u8, stream.m3u8, seg_*.ts}
set -euo pipefail
cd "$(dirname "$0")/.."

HERO="public/topshelf/hero.mp4"
OUT="public/topshelf/hls"
[ -f "$HERO" ] || { echo "error: $HERO missing — run scripts/shoot-topshelf-hero.sh first" >&2; exit 1; }

rm -rf "$OUT"; mkdir -p "$OUT"

echo "==> encode (H.264 High@4.0 1080p30, g=60 regular GOP, silent AAC, 6s VOD segments)"
ffmpeg -y -hide_banner -loglevel error \
  -i "$HERO" \
  -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 \
  -map 0:v:0 -map 1:a:0 \
  -c:v libx264 -profile:v high -level:v 4.0 -pix_fmt yuv420p \
  -r 30 -g 60 -keyint_min 60 -sc_threshold 0 \
  -crf 24 -maxrate 5M -bufsize 10M \
  -c:a aac -b:a 96k -ar 48000 -ac 2 \
  -shortest \
  -f hls -hls_time 6 -hls_playlist_type vod \
  -hls_flags independent_segments \
  -hls_segment_filename "$OUT/seg_%03d.ts" \
  "$OUT/stream.m3u8"

echo "==> compute BANDWIDTH (peak + average) for the master playlist"
peak=0; totbytes=0; totdur=0; i=0
while read -r dur; do
  f=$(printf "%s/seg_%03d.ts" "$OUT" "$i")
  [ -f "$f" ] || break
  bytes=$(stat -f%z "$f")
  bw=$(awk -v b="$bytes" -v d="$dur" 'BEGIN{printf "%d", (d>0)? b*8/d : 0}')
  [ "$bw" -gt "$peak" ] && peak=$bw
  totbytes=$((totbytes + bytes))
  totdur=$(awk -v t="$totdur" -v d="$dur" 'BEGIN{printf "%.3f", t + d}')
  i=$((i + 1))
done < <(grep -Eo '^#EXTINF:[0-9.]+' "$OUT/stream.m3u8" | sed 's/#EXTINF://')
avg=$(awk -v b="$totbytes" -v d="$totdur" 'BEGIN{printf "%d", (d>0)? b*8/d : 0}')
# round peak up to a clean ceiling so BANDWIDTH is a safe upper bound
peak=$(( (peak/100000 + 1) * 100000 ))

echo "==> write master.m3u8 (multivariant; CODECS avc1.640028=High@4.0 + mp4a.40.2=AAC-LC)"
cat > "$OUT/master.m3u8" <<EOF
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=${peak},AVERAGE-BANDWIDTH=${avg},CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=30.000
stream.m3u8
EOF

echo "==> result"
echo "    segments : $i"
echo "    duration : ${totdur}s"
echo "    size     : $(du -sh "$OUT" | cut -f1)"
echo "    BANDWIDTH: ${peak}  AVERAGE: ${avg}"
echo "    files    : $(ls "$OUT" | tr '\n' ' ')"