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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user