Initial portable pretranscode service

This commit is contained in:
SpiderFish
2026-08-26 09:53:42 +10:00
commit 093315b382
11 changed files with 1077 additions and 0 deletions
View File
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
set -euo pipefail
: "${JELLYFIN_API_KEY:?}"
IMAGE=${IMAGE:-pretranscode:test}
NETWORK=${MEDIA_NETWORK:-media-internal}
JELLYFIN_URL=${JELLYFIN_URL:-http://jellyfin:8096}
ROOT=$(mktemp -d)
trap 'rm -rf "$ROOT"' EXIT
mkdir -p "$ROOT"/{source/movies/Test,view,cache,state}
touch "$ROOT/cache/.pretranscode-cache"
docker build -q -t "$IMAGE" . >/dev/null
docker run --rm --entrypoint ffmpeg -v "$ROOT/source:/source" "$IMAGE" \
-hide_banner -loglevel error -f lavfi -i testsrc2=size=1280x720:rate=24 \
-t 6 -c:v libx265 -pix_fmt yuv420p10le -an /source/movies/Test/Test.mkv
docker run --rm --network "$NETWORK" --entrypoint python3 \
-e SOURCE_ROOT=/source -e VIEW_ROOT=/view -e CACHE_ROOT=/cache -e STATE_ROOT=/state \
-e JELLYFIN_URL="$JELLYFIN_URL" -e JELLYFIN_API_KEY \
-e JELLYFIN_LIBRARY_ROOT=/data/library -e JELLYFIN_ORIGINAL_ROOT=/data/original \
-e JELLYFIN_CACHE_ROOT=/data/cache \
-v "$ROOT/source:/source:ro" -v "$ROOT/view:/view" -v "$ROOT/cache:/cache" -v "$ROOT/state:/state" \
"$IMAGE" -c 'import importlib.machinery; m=importlib.machinery.SourceFileLoader("p","/app/pretranscode").load_module(); x=m.Manager(); assert x.read_sessions(); x.scan_inventory(); x.sync_view(); assert x.encode("movies/Test/Test.mkv",False)=="completed"'
OUTPUT=$(find "$ROOT/cache/files" -name '*.mkv' ! -name '*.partial' | head -1)
docker run --rm --entrypoint ffprobe -v "$ROOT/cache:/cache:ro" "$IMAGE" \
-v error -select_streams v:0 -show_entries stream=codec_name,pix_fmt \
-of default=nw=1 "/cache/files/$(basename "$OUTPUT")" | grep -qx 'codec_name=h264'
test "$(readlink "$ROOT/view/movies/Test/Test.mkv")" = "/data/cache/files/$(basename "$OUTPUT")"
echo "integration test passed"
+69
View File
@@ -0,0 +1,69 @@
import importlib.util
import json
import os
import tempfile
import unittest
from pathlib import Path
class CoreTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temp = tempfile.TemporaryDirectory()
root = Path(cls.temp.name)
for name in ("source", "view", "cache", "state"):
(root / name).mkdir()
(root / "cache" / ".pretranscode-cache").touch()
os.environ.update({
"SOURCE_ROOT": str(root / "source"),
"VIEW_ROOT": str(root / "view"),
"CACHE_ROOT": str(root / "cache"),
"STATE_ROOT": str(root / "state"),
"JELLYFIN_LIBRARY_ROOT": "/library",
"JELLYFIN_ORIGINAL_ROOT": "/original",
"JELLYFIN_CACHE_ROOT": "/cached",
})
spec = importlib.util.spec_from_file_location("pretranscode", Path(__file__).parents[1] / "pretranscode.py")
cls.module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cls.module)
cls.root = root
@classmethod
def tearDownClass(cls):
cls.temp.cleanup()
def test_paths_are_configurable(self):
m = self.module
self.assertEqual(m.relative_source("/library/tv/Show/episode.mkv"), "tv/Show/episode.mkv")
self.assertEqual(m.source_target("tv/Show/episode.mkv"), "/original/tv/Show/episode.mkv")
self.assertTrue(m.cache_target("tv/Show/episode.mkv").startswith("/cached/files/"))
def test_series_lock_key(self):
m = self.module
self.assertEqual(m.item_key("tv/Show/Season 1/Episode.mkv"), "series:Show")
self.assertEqual(m.item_key("movies/Movie/file.mkv"), "item:movies/Movie/file.mkv")
def test_fixed_codec_blacklist(self):
manager = self.module.Manager()
self.assertTrue(manager.incompatible({"codec": "hevc", "bit_depth": 10}))
self.assertTrue(manager.incompatible({"codec": "h264", "bit_depth": 10}))
self.assertFalse(manager.incompatible({"codec": "h264", "bit_depth": 8}))
self.assertFalse(manager.incompatible({"codec": "av1", "bit_depth": 10}))
def test_cache_requires_matching_source_fingerprint(self):
m = self.module
manager = m.Manager()
relative = "movies/Test/Test.mkv"
source = self.root / "source" / relative
source.parent.mkdir(parents=True, exist_ok=True)
source.write_bytes(b"original")
output, manifest = manager.cache_paths(relative)
output.write_bytes(b"cached")
manifest.write_text(json.dumps({"relative": relative, "source_fingerprint": m.fingerprint(source)}))
self.assertTrue(manager.cache_valid(relative))
source.write_bytes(b"changed")
self.assertFalse(manager.cache_valid(relative))
if __name__ == "__main__":
unittest.main()