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) == []