Never serve a partial file: it makes Jellyfin transcode

The 12s first-byte grace was the wrong trade and a real play found it
within the hour. A 2-hour upload took 3 minutes to start, played 6
seconds, and stalled. Jellyfin had run ffmpeg with -probesize 1G against
the growing stream and then transcoded to HLS with libx264.

The cause is the container. A fragmented MP4 with empty_moov has no
duration in its header, so the only way to get one is to sum every
fragment -- probing a growing file reads all of it. Jellyfin cannot
establish duration, codec or bitrate, so it abandons direct play and
transcodes a stream it also cannot seek. It was targeting 4.83 Mbps
against a source measured at 3.29: re-encoding a stream that already fit,
because it could not measure it.

The same video once complete reports SupportsDirectPlay with the exact
runtime and bitrate.

So FIRST_BYTE_GRACE defaults to infinite again, with --wait-timeout raised
to 600s for a 2-hour upload. A cold long video is slow to start, which is
accepted: the fetch outlives the request so a retry is instant, and a
retryable stall beats a transcode that wastes a gigabyte and cannot work.
Both failure modes are recorded at the constant in the order measured so
the 12s cap is not reintroduced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-13 11:32:12 +01:00
parent c12837cbb9
commit 22d8828080
10 changed files with 514 additions and 46 deletions
+129
View File
@@ -413,3 +413,132 @@ def test_reclassifying_a_materialised_video_removes_its_files(
assert stats["shorts"] == 1
assert videos.get(conn, "vid00000001")["state"] == videos.SKIPPED_SHORT
assert not path.exists()
# ------------------------------------------------- the min_keep_videos floor
#
# §5 defines retention as max(retention_days, min_keep_videos newest) and gives
# the reason: a channel that uploads every six weeks has nothing inside a 30-day
# window and would appear in Jellyfin as an empty series. reap.py implemented the
# second half; discovery did not, so it only protected videos that had already
# been materialised. Measured on the real 119 subscriptions: 441 episodes but 57
# channels with none, 55 of them holding skipped_old rows.
def _old(conn, channel, count, *, start=1):
"""`count` skipped_old videos, newest first at 2026-01-{start}..."""
made = []
for index in range(count):
made.append(add_video(
conn, channel["id"], f"old{index + start:08d}",
upload_date=f"2026-01-{index + start:02d}",
state=videos.SKIPPED_OLD,
))
return made
def test_a_channel_with_nothing_in_the_window_keeps_its_newest(conn, settings, channel):
_old(conn, channel, 8)
promoted = discovery.top_up_to_min_keep(conn, settings, channel)
assert promoted == 5
listed = conn.execute(
"SELECT video_id FROM video WHERE state = ? ORDER BY upload_date DESC",
(videos.LISTED,),
).fetchall()
# The five NEWEST, not the first five found.
assert [row["video_id"] for row in listed] == [
"old00000008", "old00000007", "old00000006", "old00000005", "old00000004",
]
def test_the_floor_counts_what_is_already_there(conn, settings, channel):
"""A channel with 3 in-window videos needs only 2 older ones."""
for index in range(3):
add_video(conn, channel["id"], f"new{index:08d}", upload_date="2026-08-10")
_old(conn, channel, 6)
assert discovery.top_up_to_min_keep(conn, settings, channel) == 2
def test_a_busy_channel_is_untouched(conn, settings, channel):
for index in range(9):
add_video(conn, channel["id"], f"new{index:08d}", upload_date="2026-08-10")
_old(conn, channel, 4)
assert discovery.top_up_to_min_keep(conn, settings, channel) == 0
assert conn.execute(
"SELECT count(*) FROM video WHERE state = ?", (videos.SKIPPED_OLD,)
).fetchone()[0] == 4
def test_topping_up_is_idempotent(conn, settings, channel):
"""It runs every poll, every hour. Twice must not mean ten episodes."""
_old(conn, channel, 8)
first = discovery.top_up_to_min_keep(conn, settings, channel)
second = discovery.top_up_to_min_keep(conn, settings, channel)
assert (first, second) == (5, 0)
assert conn.execute(
"SELECT count(*) FROM video WHERE state = ?", (videos.LISTED,)
).fetchone()[0] == 5
def test_aged_out_videos_are_never_revived(conn, settings, channel):
"""They were on disk and were deleted. Reviving them presents months of old
episodes to Jellyfin as new, which is what the tombstone exists to stop."""
for index in range(6):
add_video(conn, channel["id"], f"gone{index:08d}",
upload_date=f"2026-02-{index + 1:02d}", state=videos.AGED_OUT)
assert discovery.top_up_to_min_keep(conn, settings, channel) == 0
assert conn.execute(
"SELECT count(*) FROM video WHERE state = ?", (videos.AGED_OUT,)
).fetchone()[0] == 6
def test_shorts_and_livestreams_do_not_count_towards_the_floor(conn, settings, channel):
"""They are excluded by policy and can never be episodes, so a channel whose
newest uploads are all Shorts must reach further back for long-form ones."""
for index in range(4):
add_video(conn, channel["id"], f"shrt{index:08d}",
upload_date="2026-08-11", state=videos.SKIPPED_SHORT)
add_video(conn, channel["id"], "live0000001",
upload_date="2026-08-11", state=videos.SKIPPED_LIVE)
_old(conn, channel, 7)
assert discovery.top_up_to_min_keep(conn, settings, channel) == 5
def test_a_floor_of_zero_disables_it(conn, settings, channel):
settings.set("min_keep_videos", "0")
_old(conn, channel, 6)
assert discovery.top_up_to_min_keep(conn, settings, channel) == 0
def test_a_channel_with_no_videos_at_all_stays_empty(conn, settings, channel):
"""Two of the real 119 have no long-form uploads whatsoever (their UULF
playlist 404s). There is nothing to promote and no directory should appear."""
assert discovery.top_up_to_min_keep(conn, settings, channel) == 0
def test_promoted_videos_survive_the_next_reap(conn, settings, channel):
"""The whole point: the two halves of max(window, N newest) must agree. If
reap deleted what discovery just promoted, channels would flicker hourly."""
from ytstream import reap
_old(conn, channel, 5)
discovery.top_up_to_min_keep(conn, settings, channel)
for row in conn.execute(
"SELECT video_id FROM video WHERE state = ?", (videos.LISTED,)
).fetchall():
videos.mark_materialised(
conn, row["video_id"], rel_path=f"c/{row['video_id']}.strm",
season=2026, episode=1, upload_date="2026-01-01", duration=900,
title="t",
)
assert reap.candidates(conn, settings) == []
+27 -4
View File
@@ -341,14 +341,37 @@ def test_growing_flag_means_no_wait_at_all(tmp_path):
server.close()
def test_status_reports_the_serving_mode_and_grace(manager_factory):
"""/healthz has to show this: it is the difference between 'plays' and
'playback error', and it is otherwise invisible without the unit file."""
def test_the_default_is_to_wait_for_a_complete_file(manager_factory):
"""The default must not serve a partial file. Jellyfin responds to one by
dragging a gigabyte through the proxy to probe a fragmented MP4 that has no
duration in its header, then transcoding a stream it cannot seek: measured
2026-08-13, 3 minutes to start, 6 seconds of video, permanent stall."""
assert proxy.FIRST_BYTE_GRACE == float("inf")
status = manager_factory().status()
assert status["mode"] == "wait-for-complete"
assert status["first_byte_grace_s"] is None
def test_status_is_valid_json_with_no_grace_cap(manager_factory):
"""json.dumps emits `Infinity` for an infinite float, which is not JSON and
breaks any client that parses /healthz strictly."""
payload = json.dumps(manager_factory().status())
assert "Infinity" not in payload
assert json.loads(payload)["first_byte_grace_s"] is None
def test_status_reports_a_finite_grace_when_one_is_set(manager_factory):
manager = manager_factory()
manager.strict = False
manager.first_byte_grace = 12.0
status = manager.status()
assert status["mode"] == "wait-then-stream"
assert status["first_byte_grace_s"] == proxy.FIRST_BYTE_GRACE
assert status["first_byte_grace_s"] == 12.0
def test_status_reports_growing_mode(manager_factory):
growing = manager_factory(growing=True)
assert growing.status()["mode"] == "growing"
assert growing.status()["first_byte_grace_s"] == 0.0
+34
View File
@@ -230,3 +230,37 @@ def test_summarise_is_a_single_line():
assert "channels=119" in text
assert "materialised=7" in text
assert "aged_out=8" in text
def test_run_prunes_channels_that_have_nothing_to_show(conn, settings, media_root,
monkeypatch):
"""End to end: a subscribed channel whose directory exists only because
subscribe() wrote tvshow.nfo must not survive a run as an empty series."""
from conftest import add_channel
from ytstream import runner
empty = add_channel(conn, "UC" + "s" * 22, "Dead Channel", "dead-channel")
tree = media_root / "dead-channel"
tree.mkdir()
(tree / "tvshow.nfo").write_text("<tvshow/>")
assert runner.prune_empty_channels(conn) == 1
assert not tree.exists()
def test_run_keeps_a_channel_with_a_materialised_video(conn, settings, media_root):
from conftest import add_channel, add_video
from ytstream import runner, videos
row = add_channel(conn, "UC" + "t" * 22, "Alive", "alive")
tree = media_root / "alive"
(tree / "Season 2026").mkdir(parents=True)
(tree / "Season 2026" / "ep.strm").write_text("url")
video = add_video(conn, row["id"], "vid00000009")
videos.mark_materialised(conn, "vid00000009",
rel_path="alive/Season 2026/ep.strm", season=2026,
episode=1, upload_date="2026-08-01", duration=900,
title="t")
assert runner.prune_empty_channels(conn) == 0
assert tree.exists()
+52
View File
@@ -310,3 +310,55 @@ def test_a_renamed_episode_removes_its_old_files(conn, settings, media_root,
assert not old.exists()
assert (media_root / second["rel_path"]).exists()
assert len(list(media_root.rglob("*.strm"))) == 1
# ------------------------------------- pruning channels with nothing to show
#
# §5: a channel with nothing inside the window must not leave an empty series in
# Jellyfin. runner creates directories lazily to honour that, but
# channels.subscribe() mkdirs to write tvshow.nfo and a poster, so every
# subscription got one regardless. Measured after approving all 119 real
# subscriptions: 57 empty series.
def test_prune_removes_a_channel_with_no_episodes(conn, media_root, channel):
tree = media_root / "clabretro"
tree.mkdir()
(tree / "tvshow.nfo").write_text("<tvshow/>")
(tree / "poster.jpg").write_bytes(b"x" * 100)
assert strm.prune_if_no_episodes(channel) is True
assert not tree.exists()
def test_prune_leaves_a_channel_that_has_episodes(conn, settings, media_root,
channel, video, no_thumbs):
strm.materialise(conn, settings, channel, video)
assert strm.prune_if_no_episodes(channel) is False
assert (media_root / "clabretro").exists()
def test_prune_finds_episodes_in_any_season(conn, media_root, channel):
"""The .strm lives a directory down, so a shallow check would delete it."""
deep = media_root / "clabretro" / "Season 2019"
deep.mkdir(parents=True)
(deep / "clabretro - S2019E1010 - Old [dQw4w9WgXcQ].strm").write_text("url")
assert strm.prune_if_no_episodes(channel) is False
assert deep.exists()
def test_prune_is_a_no_op_when_there_is_no_directory(conn, channel):
assert strm.prune_if_no_episodes(channel) is False
def test_prune_refuses_a_blank_channel_directory(conn, media_root):
from conftest import add_channel
row = add_channel(conn, "UC" + "r" * 22, "Blank", "")
keep = media_root / "keep"
keep.mkdir()
assert strm.prune_if_no_episodes(row) is False
assert keep.exists()