perf(jellyfin): skip per-folder user data in folder tree listings

This commit is contained in:
edde746
2026-06-12 02:18:28 +02:00
parent d816d82a38
commit 0f1bf5e011
4 changed files with 121 additions and 197 deletions
-143
View File
@@ -1,143 +0,0 @@
#!/usr/bin/env bash
# Generates MKV test assets that prove (or disprove) frame-perfect ASS rendering:
# the video has a per-frame counter burned in (top) and the MKV carries an ASS
# track flipping the same counter (bottom) on exactly the same frame boundaries.
# Any captured frame showing video number N with subtitle number != N is a sync
# failure.
#
# Outputs (in scripts/framesync/):
# framesync-2397.mkv 23.976 fps, plain per-frame counter events
# framesync-60.mkv 60 fps variant
# framesync-stress.mkv 23.976 fps + heavy animated typesetting (blur/scale/
# rotation re-rendered every frame) to stress libass
#
# The burn-in uses, in order of preference: ffmpeg drawtext, ffmpeg subtitles
# filter, or mpv's encoding mode (mpv always bundles libass) — all three
# evaluate the counter at each frame's exact PTS, so the burned reference is
# frame-exact by construction.
#
# Verification procedure (Android, ExoPlayer path):
# 1. Play the file with the ASS track selected (SDR content and the
# ASS-tunneling block mean the layers are screen-recordable).
# 2. adb shell screenrecord /sdcard/sync.mp4 (record ~30 s, pull it)
# 3. Step through frames (e.g. ffmpeg -i sync.mp4 frames/%05d.png, or mpv with
# '.' frame-step): top (video) and bottom (player-rendered ASS) counters
# must match on EVERY captured frame. Desktop mpv is the reference player.
# 4. Cross-check at the compositor: while playing,
# adb shell dumpsys SurfaceFlinger --latency
# lists per-layer (desired, actual) present times for the video SurfaceView
# and the ASS overlay layer — matched content must share vsync timestamps.
# 5. Cheap regression: the in-app stats overlay should show subLateSwaps ≈ 0,
# subOverflows == 0, subMinLeadMs ≥ 0 and a >95% subSpecHits ratio.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
mkdir -p framesync
cd framesync
DURATION_S=${DURATION_S:-60}
# drawtext resolves the family via fontconfig; override with FONT if needed.
FONT=${FONT:-Sans}
have_filter() { ffmpeg -hide_banner -filters 2>/dev/null | grep -q " $1 "; }
# Emits an ASS file with one Dialogue event per frame. Frame i starts at
# i*den/num seconds; timestamps are floored to ASS centisecond precision, which
# is safe because the floor error (<10 ms) is smaller than any frame interval
# generated here (16.7 / 41.7 ms) — an event can never leak onto the previous
# frame. burn=1 emits the top-aligned white reference style (for burn-in),
# burn=0 the bottom-aligned yellow player style; heavy=1 adds per-frame
# animated blur/scale/rotation events.
gen_ass() { # $1=fps_num $2=fps_den $3=frames $4=burn(0/1) $5=heavy(0/1) $6=outfile
awk -v num="$1" -v den="$2" -v frames="$3" -v burn="$4" -v heavy="$5" '
function ts(cs, h, m, s) {
h = int(cs / 360000); cs -= h * 360000
m = int(cs / 6000); cs -= m * 6000
s = int(cs / 100); cs -= s * 100
return sprintf("%d:%02d:%02d.%02d", h, m, s, cs)
}
BEGIN {
print "[Script Info]"
print "ScriptType: v4.00+"
print "PlayResX: 1280"
print "PlayResY: 720"
print ""
print "[V4+ Styles]"
print "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"
if (burn)
print "Style: Counter,Arial,150,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,1,5,0,8,10,10,30,1"
else
print "Style: Counter,Arial,150,&H0000FFFF,&H000000FF,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,1,5,0,2,10,10,30,1"
print "Style: Stress,Arial,80,&H40FF8800,&H000000FF,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,1,2,0,5,10,10,10,1"
print ""
print "[Events]"
print "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"
for (i = 0; i < frames; i++) {
start = int(i * den * 100 / num)
end = int((i + 1) * den * 100 / num)
if (end <= start) end = start + 1
printf "Dialogue: 0,%s,%s,Counter,,0,0,0,,%d\n", ts(start), ts(end), i
if (heavy) {
# Re-emitted every frame with a frame-dependent rotation/blur so libass
# reports changed content and re-rasterizes large blurred glyphs —
# approximates typesetting storms (the measured 100-400 ms frames).
printf "Dialogue: 1,%s,%s,Stress,,0,0,0,,{\\an5\\pos(640,260)\\blur12\\fscx320\\fscy320\\frz%d}#\n", ts(start), ts(end), (i * 7) % 360
printf "Dialogue: 1,%s,%s,Stress,,0,0,0,,{\\an5\\pos(640,500)\\blur18\\fscx500\\fscy220\\frz%d\\alpha&H80&}@\n", ts(start), ts(end), 359 - (i * 11) % 360
}
}
}
' /dev/null > "$6"
}
# Produces the reference video: gray frames with the frame counter burned in at
# the top. Tries drawtext, then the subtitles filter, then mpv encoding.
gen_video() { # $1=rate $2=burn_ass $3=outfile
if have_filter drawtext; then
ffmpeg -y -v error -f lavfi -i "color=c=0x202020:s=1280x720:r=$1" \
-vf "drawtext=font='${FONT}':text='%{n}':fontsize=150:fontcolor=white:borderw=5:bordercolor=black:x=(w-text_w)/2:y=30" \
-t "$DURATION_S" -c:v libx264 -preset veryfast -crf 18 -pix_fmt yuv420p "$3"
elif have_filter subtitles; then
ffmpeg -y -v error -f lavfi -i "color=c=0x202020:s=1280x720:r=$1" \
-vf "subtitles=$2" \
-t "$DURATION_S" -c:v libx264 -preset veryfast -crf 18 -pix_fmt yuv420p "$3"
elif command -v mpv > /dev/null; then
ffmpeg -y -v error -f lavfi -i "color=c=0x202020:s=1280x720:r=$1" \
-t "$DURATION_S" -c:v libx264 -preset veryfast -crf 18 -pix_fmt yuv420p blank.mp4
mpv blank.mp4 --sub-files="$2" --vf=sub --no-audio \
--o="$3" --of=mp4 --ovc=libx264 --ovcopts=preset=veryfast,crf=18 \
--msg-level=all=error
rm -f blank.mp4
else
echo "error: need ffmpeg with drawtext or subtitles filter, or mpv (for the burn-in)" >&2
exit 1
fi
}
# Muxes the burned video + silent audio + the player-rendered ASS track.
mux() { # $1=video $2=ass $3=outfile
ffmpeg -y -v error -i "$1" -f lavfi -i "anullsrc=r=48000:cl=stereo" -i "$2" \
-map 0:v -map 1:a -map 2 -c:v copy -c:a aac -b:a 64k -c:s copy -shortest \
-metadata:s:s:0 language=eng -disposition:s:0 default "$3"
}
frames_2397=$(awk -v d="$DURATION_S" 'BEGIN { print int(d * 24000 / 1001) }')
frames_60=$(awk -v d="$DURATION_S" 'BEGIN { print d * 60 }')
echo "Generating ${DURATION_S}s assets..."
gen_ass 24000 1001 "$frames_2397" 1 0 burn-2397.ass
gen_ass 60 1 "$frames_60" 1 0 burn-60.ass
gen_ass 24000 1001 "$frames_2397" 0 0 counter-2397.ass
gen_ass 60 1 "$frames_60" 0 0 counter-60.ass
gen_ass 24000 1001 "$frames_2397" 0 1 counter-stress.ass
gen_video "24000/1001" burn-2397.ass video-2397.mp4
gen_video "60" burn-60.ass video-60.mp4
mux video-2397.mp4 counter-2397.ass framesync-2397.mkv
mux video-60.mp4 counter-60.ass framesync-60.mkv
mux video-2397.mp4 counter-stress.ass framesync-stress.mkv
rm -f video-2397.mp4 video-60.mp4 burn-2397.ass burn-60.ass
echo "Done:"
ls -la framesync-*.mkv
echo
echo "Reference check: open framesync-2397.mkv in mpv, frame-step with '.' —"
echo "top (video) and bottom (subtitle) counters must match on every frame."
echo "Then repeat in Plezy on-device and screenrecord (see header comments)."