feat(playback): pre-transcode HEVC videos in background; veryfast preset

Long videos blocked /playback for the entire encode duration. The fix
is to populate the cache before the user clicks, not when they click.

Changes:
- Extract ffprobe + ffmpeg helpers to services/video.py so the request
  handler and the background task share one sync implementation. The
  endpoint wraps calls in asyncio.to_thread; celery just calls them.
- New tasks/video.py with pretranscode_video. Idempotent: skips when
  the cache is already current and skips passthrough-safe sources
  (h264 in mp4/m4v/webm). 30-min task time limit so the long-tail
  files (3GP archive, multi-minute 1080p clips) still complete.
- scan_folder now dispatches pretranscode_video alongside
  generate_thumbnails / extract_metadata for any new video row.
- POST /library/maintenance/backfill-video-cache enqueues every
  active video so the existing library catches up.
- libx264 preset bumped from fast to veryfast. ~2x throughput on this
  CPU-only box, output a few % larger but well within disk budget.
- /playback simplifies to: cache check, passthrough if h264 in
  web-safe container, else sync transcode (still there as fallback
  for races against the queued task).

Once the backfill task drains, /playback should be near-instant for
every video. Any video added afterwards is pre-transcoded at scan
time, so the user keeps that property going forward.
This commit is contained in:
Claudio
2026-05-12 00:03:15 +02:00
parent 2a5270d399
commit c4df92720b
6 changed files with 253 additions and 127 deletions

View File

@@ -18,6 +18,7 @@ celery_app = Celery(
include=[
'app.tasks.scan',
'app.tasks.thumbs',
'app.tasks.video',
'app.tasks.vision',
'app.services.metadata', # extract_metadata lives here
]
@@ -52,6 +53,10 @@ celery_app.conf.update(
'scan_folder': {'queue': 'low'},
'scan_all_source_roots': {'queue': 'low'},
'backfill_gps': {'queue': 'low'},
# Pre-transcode HEVC videos in the background so /playback is a
# cache hit on first user click. CPU-heavy but tolerant of the
# low-priority queue (it doesn't block any user-facing flow).
'pretranscode_video': {'queue': 'low'},
# `watch_folders` is retired (file events come from NC webhooks)
# but the task definition still exists as a no-op shim for any
# in-flight apply_async. Route it to the default queue so the