#!/usr/bin/env bash set -eu PROJECT="/home/nvme/dockerdata/prove/s157js" VIDEO="${1:-$PROJECT/public/photos/Fabio/original/2017Irlanda19-29ago/VID-20260920-WA0000.mp4}" OUT="${2:-/tmp/prova-wait}" PORT="${PORT:-9999}" SEGMENT_TIMEOUT="${SEGMENT_TIMEOUT:-300}" FFMPEG="/usr/local/bin/ffmpeg" FFPROBE="/usr/local/bin/ffprobe" WAIT_SERVER="$PROJECT/hls_cors_server_wait.py" SERVER_PID="" FFMPEG_PID="" cleanup() { echo echo "Arresto dei processi..." if [ -n "$FFMPEG_PID" ]; then kill "$FFMPEG_PID" 2>/dev/null || true fi if [ -n "$SERVER_PID" ]; then kill "$SERVER_PID" 2>/dev/null || true fi wait 2>/dev/null || true } trap cleanup EXIT INT TERM if [ ! -f "$VIDEO" ]; then echo "ERRORE: video non trovato:" echo "$VIDEO" exit 1 fi if [ ! -f "$WAIT_SERVER" ]; then echo "ERRORE: server con attesa non trovato:" echo "$WAIT_SERVER" echo echo "Deve esistere hls_cors_server_wait.py nel progetto." exit 1 fi echo "==================================================" echo "PROVA VOD ANTICIPATA" echo "==================================================" echo "Video: $VIDEO" echo "Output: $OUT" echo "Porta: $PORT" echo "Timeout: $SEGMENT_TIMEOUT secondi" echo rm -rf "$OUT" mkdir -p "$OUT" echo "Avvio server HTTP con attesa dei segmenti..." python3 "$WAIT_SERVER" \ "$OUT" \ --port "$PORT" \ --segment-timeout "$SEGMENT_TIMEOUT" & SERVER_PID=$! sleep 0.5 if ! kill -0 "$SERVER_PID" 2>/dev/null; then echo "ERRORE: il server HTTP non è partito." exit 1 fi echo echo "Avvio FFmpeg in modalità realtime..." echo "$FFMPEG" \ -hide_banner \ -loglevel warning \ -y \ -noautorotate \ -i "$VIDEO" \ -map 0:v:0 \ -map '0:a:0?' \ -vf "scale=1280:720:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1280:720:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1,format=yuv420p" \ -metadata:s:v:0 rotate=0 \ -c:v libx264 \ -profile:v high \ -level:v 4.1 \ -preset ultrafast \ -tune zerolatency \ -b:v 5000k \ -maxrate 6000k \ -bufsize 12000k \ -r 30 \ -g 90 \ -keyint_min 90 \ -sc_threshold 0 \ -c:a aac \ -b:a 160k \ -ac 2 \ -ar 48000 \ -f hls \ -hls_time 3 \ -hls_list_size 0 \ -hls_playlist_type event \ -hls_segment_type mpegts \ -hls_flags independent_segments \ -hls_segment_filename "$OUT/segment_%05d.ts" \ "$OUT/internal.m3u8" & FFMPEG_PID=$! echo "FFmpeg PID: $FFMPEG_PID" echo echo "Attendo la chiusura dei primi 3 segmenti..." # La comparsa del segmento 3 indica che i segmenti 0, 1 e 2 # sono già stati chiusi da FFmpeg. while [ ! -s "$OUT/segment_00003.ts" ]; do if ! kill -0 "$FFMPEG_PID" 2>/dev/null; then echo "ERRORE: FFmpeg è terminato prima di produrre 3 segmenti." wait "$FFMPEG_PID" || true exit 1 fi COUNT="$( find "$OUT" \ -maxdepth 1 \ -type f \ -name 'segment_*.ts' \ 2>/dev/null \ | wc -l \ | tr -d ' ' )" echo "Segmenti visibili: $COUNT" sleep 0.2 done echo echo "Primi 3 segmenti completi." echo "Creazione della playlist VOD anticipata..." "$FFPROBE" \ -v error \ -show_entries format=duration \ -of default=noprint_wrappers=1:nokey=1 \ "$VIDEO" \ > "$OUT/.duration.txt" python3 - "$OUT" <<'PY' from pathlib import Path import math import sys out = Path(sys.argv[1]).resolve() duration_file = out / ".duration.txt" playlist = out / "cast.m3u8" temporary = out / "cast.m3u8.tmp" duration = float( duration_file.read_text( encoding="utf-8" ).strip() ) segment_duration = 3.0 total_segments = math.ceil( duration / segment_duration ) last_duration = ( duration - segment_duration * (total_segments - 1) ) if last_duration <= 0: last_duration = segment_duration lines = [ "#EXTM3U", "#EXT-X-VERSION:6", "#EXT-X-TARGETDURATION:3", "#EXT-X-MEDIA-SEQUENCE:0", "#EXT-X-START:TIME-OFFSET=0,PRECISE=YES", "#EXT-X-PLAYLIST-TYPE:VOD", "#EXT-X-INDEPENDENT-SEGMENTS", ] for index in range(total_segments): current_duration = ( segment_duration if index < total_segments - 1 else last_duration ) lines.append( f"#EXTINF:{current_duration:.6f}," ) lines.append( f"segment_{index:05d}.ts" ) lines.append("#EXT-X-ENDLIST") temporary.write_text( "\n".join(lines) + "\n", encoding="utf-8", ) temporary.replace(playlist) real_segments = len( list(out.glob("segment_*.ts")) ) print() print("Playlist VOD anticipata pubblicata") print(f"File: {playlist}") print(f"Durata: {duration:.6f} secondi") print(f"Segmenti dichiarati: {total_segments}") print(f"Segmenti già presenti: {real_segments}") print(f"Ultimo EXTINF: {last_duration:.6f}") PY IP_ADDRESS="$( hostname -I \ | awk '{print $1}' )" echo echo "==================================================" echo "PLAYLIST PRONTA" echo "==================================================" echo echo "URL:" echo "http://$IP_ADDRESS:$PORT/cast.m3u8" echo echo "Dal Mac esegui:" echo echo "catt -d 192.168.1.15 stop" echo "catt -d 192.168.1.15 cast \\" echo " \"http://$IP_ADDRESS:$PORT/cast.m3u8\"" echo echo "Per provare un seek futuro:" echo echo "catt -d 192.168.1.15 seek 90" echo echo "Il server mostrerà WAIT e READY se il segmento" echo "richiesto non è ancora disponibile." echo "==================================================" echo # Attende il completamento della transcodifica. set +e wait "$FFMPEG_PID" FFMPEG_STATUS=$? set -e FFMPEG_PID="" if [ "$FFMPEG_STATUS" -eq 0 ]; then echo echo "FFmpeg ha completato la conversione." echo "Pubblicazione della playlist reale definitiva..." python3 - "$OUT" <<'PY' from pathlib import Path import sys out = Path(sys.argv[1]).resolve() internal = out / "internal.m3u8" public = out / "cast.m3u8" temporary = out / "cast.m3u8.final.tmp" if not internal.is_file(): raise SystemExit( f"ERRORE: playlist interna mancante: {internal}" ) content = internal.read_text( encoding="utf-8" ) if "#EXT-X-ENDLIST" not in content: raise SystemExit( "ERRORE: la playlist interna non contiene ENDLIST." ) content = content.replace( "#EXT-X-PLAYLIST-TYPE:EVENT", "#EXT-X-PLAYLIST-TYPE:VOD", ) start_marker = ( "#EXT-X-START:TIME-OFFSET=0,PRECISE=YES" ) if start_marker not in content: lines = content.splitlines() version_index = next( ( index for index, line in enumerate(lines) if line.startswith("#EXT-X-VERSION:") ), 0, ) lines.insert( version_index + 1, start_marker, ) content = "\n".join(lines) + "\n" elif not content.endswith("\n"): content += "\n" temporary.write_text( content, encoding="utf-8", ) temporary.replace(public) print(f"Playlist finale pubblicata: {public}") PY else echo echo "ERRORE: FFmpeg è terminato con codice $FFMPEG_STATUS" fi echo echo "Il server HTTP resta attivo." echo "Premi Ctrl+C per terminare la prova." wait "$SERVER_PID"