#!/bin/bash set -euo pipefail IMAGE=${IMAGE:-localhost/pretranscode-c-review} ROOT=$(mktemp -d) PORT=$(python3 - <<'PY' import socket s=socket.socket(); s.bind(('127.0.0.1', 0)); print(s.getsockname()[1]); s.close() PY ) PID= CONTAINER= cleanup() { [ -z "$CONTAINER" ] || podman rm -f "$CONTAINER" >/dev/null 2>&1 || true [ -z "$PID" ] || kill "$PID" >/dev/null 2>&1 || true rm -rf "$ROOT" } trap cleanup EXIT mkdir -p "$ROOT"/{source/movies/Test,view,cache/files,state} touch "$ROOT/cache/.pretranscode-cache" printf partial >"$ROOT/cache/files/failed.partial" printf orphan >"$ROOT/cache/files/orphan.bin" cat >"$ROOT/source/movies/Test/embedded.srt" <<'EOF' 1 00:00:00,000 --> 00:00:05,000 Compatibility test EOF podman run --rm --userns=keep-id --entrypoint ffmpeg -v "$ROOT/source:/source:Z" "$IMAGE" \ -hide_banner -loglevel error -f lavfi -i testsrc2=size=1280x720:rate=24 \ -f lavfi -i sine=frequency=440:sample_rate=48000 \ -f srt -i /source/movies/Test/embedded.srt -t 6 \ -map 0:v:0 -map 1:a:0 -map 2:s:0 -c:v libx265 -pix_fmt yuv420p10le \ -c:a eac3 -c:s srt /source/movies/Test/Test.mkv TEST_VIEW="$ROOT/view" TEST_STATE="$ROOT/jellyfin-state.json" \ python3 "$(dirname "$0")/fake_jellyfin.py" "$PORT" & PID=$! sleep 0.2 CONTAINER=pretranscode-c-integration-$RANDOM podman run -d --userns=keep-id --name "$CONTAINER" \ --add-host host.containers.internal:host-gateway \ -e SOURCE_ROOT=/source -e VIEW_ROOT=/view -e CACHE_ROOT=/cache -e STATE_ROOT=/state \ -e JELLYFIN_URL="http://host.containers.internal:$PORT" -e JELLYFIN_API_KEY=test \ -e JELLYFIN_LIBRARY_ROOT=/data/library -e JELLYFIN_ORIGINAL_ROOT=/data/original \ -e JELLYFIN_CACHE_ROOT=/data/cache -e POLL_SECONDS=1 -e RESCAN_SECONDS=1 \ -e H264_PRESET=ultrafast \ -v "$ROOT/source:/source:ro,Z" -v "$ROOT/view:/view:Z" \ -v "$ROOT/cache:/cache:Z" -v "$ROOT/state:/state:Z" "$IMAGE" >/dev/null for _ in $(seq 1 90); do LINK=$(readlink "$ROOT/view/movies/Test/Test.mkv" 2>/dev/null || true) case "$LINK" in /data/cache/files/*) break ;; esac if [ "$(podman inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || true)" != true ]; then podman logs "$CONTAINER" >&2 || true exit 1 fi sleep 1 done LINK=$(readlink "$ROOT/view/movies/Test/Test.mkv" 2>/dev/null || true) if [[ "$LINK" != /data/cache/files/* ]]; then podman logs "$CONTAINER" >&2 || true cat "$ROOT/state/ffmpeg.log" >&2 2>/dev/null || true exit 1 fi [[ "$LINK" == /data/cache/files/* ]] BASENAME=${LINK##*/} [[ "$BASENAME" =~ ^[0-9a-f]{32}\.[0-9a-f]{32}\.[0-9a-f]{32}\.[0-9a-f]{32}\.(mkv|mp4)$ ]] OUTPUT="$ROOT/cache/${LINK#/data/cache/}" test -s "$OUTPUT" ! find "$ROOT/cache/files" -type f \( -name '*.json' -o -name '*.partial' \) | grep -q . test ! -e "$ROOT/cache/files/orphan.bin" podman run --rm --userns=keep-id --entrypoint ffprobe -v "$ROOT/cache:/cache:ro,Z" "$IMAGE" \ -v error -show_entries stream=codec_type,codec_name,pix_fmt,channels:stream_side_data=side_data_type \ -show_entries chapter=id -of json "/cache/${LINK#/data/cache/}" | python3 -c ' import json,sys d=json.load(sys.stdin); streams=d.get("streams",[]) video=[s for s in streams if s.get("codec_type")=="video"] audio=[s for s in streams if s.get("codec_type")=="audio"] subs=[s for s in streams if s.get("codec_type")=="subtitle"] assert len(video)==1 and video[0].get("codec_name")=="h264" and video[0].get("pix_fmt")=="yuv420p" assert len(audio)==1 and audio[0].get("codec_name")=="aac" and audio[0].get("channels")==2 assert not subs and not d.get("chapters") assert not any("dovi" in x.get("side_data_type","").lower() for x in video[0].get("side_data_list",[])) ' python3 - "$ROOT/jellyfin-state.json" <<'PY' import json, sys state=json.load(open(sys.argv[1])) assert state["codec"] == "h264" and state["depth"] == 8 assert state["attempts"] >= 2 PY # A profile change, including a published 1.x object, keeps the old object live # until its replacement exists. podman stop -t 10 "$CONTAINER" >/dev/null podman rm "$CONTAINER" >/dev/null CONTAINER= mv "$OUTPUT" "$ROOT/cache/files/legacy.mkv" ln -sfn /data/cache/files/legacy.mkv "$ROOT/view/movies/Test/Test.mkv" OLD_LINK=/data/cache/files/legacy.mkv podman run --rm --userns=keep-id \ --add-host host.containers.internal:host-gateway \ -e SOURCE_ROOT=/source -e VIEW_ROOT=/view -e CACHE_ROOT=/cache -e STATE_ROOT=/state \ -e JELLYFIN_URL="http://host.containers.internal:$PORT" -e JELLYFIN_API_KEY=test \ -e JELLYFIN_LIBRARY_ROOT=/data/library -e JELLYFIN_ORIGINAL_ROOT=/data/original \ -e JELLYFIN_CACHE_ROOT=/data/cache -e POLL_SECONDS=1 -e RESCAN_SECONDS=1 \ -e H264_PRESET=ultrafast -e H264_CRF=19 \ -v "$ROOT/source:/source:ro,Z" -v "$ROOT/view:/view:Z" \ -v "$ROOT/cache:/cache:Z" -v "$ROOT/state:/state:Z" "$IMAGE" sync [[ "$(readlink "$ROOT/view/movies/Test/Test.mkv")" == "$OLD_LINK" ]] test -s "$ROOT/cache/${OLD_LINK#/data/cache/}" CONTAINER=pretranscode-c-integration-$RANDOM podman run -d --userns=keep-id --name "$CONTAINER" \ --add-host host.containers.internal:host-gateway \ -e SOURCE_ROOT=/source -e VIEW_ROOT=/view -e CACHE_ROOT=/cache -e STATE_ROOT=/state \ -e JELLYFIN_URL="http://host.containers.internal:$PORT" -e JELLYFIN_API_KEY=test \ -e JELLYFIN_LIBRARY_ROOT=/data/library -e JELLYFIN_ORIGINAL_ROOT=/data/original \ -e JELLYFIN_CACHE_ROOT=/data/cache -e POLL_SECONDS=1 -e RESCAN_SECONDS=1 \ -e H264_PRESET=ultrafast -e H264_CRF=19 \ -v "$ROOT/source:/source:ro,Z" -v "$ROOT/view:/view:Z" \ -v "$ROOT/cache:/cache:Z" -v "$ROOT/state:/state:Z" "$IMAGE" >/dev/null for _ in $(seq 1 90); do LINK=$(readlink "$ROOT/view/movies/Test/Test.mkv" 2>/dev/null || true) [[ "$LINK" == /data/cache/files/* && "$LINK" != "$OLD_LINK" ]] && break sleep 1 done [[ "$LINK" == /data/cache/files/* && "$LINK" != "$OLD_LINK" ]] for _ in $(seq 1 10); do test ! -e "$ROOT/cache/${OLD_LINK#/data/cache/}" && break sleep 1 done test ! -e "$ROOT/cache/${OLD_LINK#/data/cache/}" podman stop -t 10 "$CONTAINER" >/dev/null podman rm "$CONTAINER" >/dev/null CONTAINER= echo "integration test passed"