feat: web video transcoding, admin playback, and viewer fixes
CI / skip-ci-check (pull_request) Successful in 1m4s
CI / lint-and-type-check (pull_request) Has been cancelled
CI / python-lint (pull_request) Has been cancelled
CI / test-backend (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / secret-scanning (pull_request) Has been cancelled
CI / dependency-scan (pull_request) Has been cancelled
CI / sast-scan (pull_request) Has been cancelled
CI / workflow-summary (pull_request) Has been cancelled
CI / skip-ci-check (pull_request) Successful in 1m4s
CI / lint-and-type-check (pull_request) Has been cancelled
CI / python-lint (pull_request) Has been cancelled
CI / test-backend (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / secret-scanning (pull_request) Has been cancelled
CI / dependency-scan (pull_request) Has been cancelled
CI / sast-scan (pull_request) Has been cancelled
CI / workflow-summary (pull_request) Has been cancelled
Add on-demand H.264/AAC web playback (RQ, ffmpeg) with API routes and Next.js proxies; extend admin UI with WebPlaybackVideo and shared hooks. Store transcode cache beside pending-photos (WEB_VIDEO_CACHE_DIR / UPLOAD_DIR) and ignore data/web_videos. Centralize FastAPI URL helpers, optional Vite and Next base paths for subfolder deploy, and fix modal reopen by using router.replace when closing the home photo viewer. Include migration, install scripts, deployment doc updates, and CI admin build env tweak. Made-with: Cursor
This commit is contained in:
@@ -345,3 +345,78 @@ def process_faces_task(
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def transcode_video_for_web_task(photo_id: int) -> dict:
|
||||
"""Background job: build browser-safe MP4 (or mark original as ready if H.264/AAC)."""
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from backend.db.models import Photo
|
||||
from backend.db.session import SessionLocal
|
||||
from backend.services.web_video_service import (
|
||||
derived_mp4_path,
|
||||
expire_web_playable_if_stale,
|
||||
probe_browser_safe_video,
|
||||
run_ffmpeg_web_transcode,
|
||||
web_playback_is_ready,
|
||||
)
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
photo = (
|
||||
db.query(Photo)
|
||||
.filter(Photo.id == photo_id)
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
if not photo or photo.media_type != "video":
|
||||
return {"ok": False, "error": "not_found"}
|
||||
|
||||
expire_web_playable_if_stale(photo)
|
||||
if web_playback_is_ready(photo):
|
||||
db.commit()
|
||||
return {"ok": True, "skipped": True}
|
||||
|
||||
photo.web_transcode_status = "running"
|
||||
photo.web_transcode_error = None
|
||||
db.commit()
|
||||
|
||||
if not os.path.isfile(photo.path):
|
||||
raise RuntimeError("Source video file not found on disk")
|
||||
|
||||
if probe_browser_safe_video(photo.path):
|
||||
photo.web_playable_path = photo.path
|
||||
photo.web_transcode_status = "ready"
|
||||
photo.web_transcode_error = None
|
||||
db.commit()
|
||||
return {"ok": True, "native": True}
|
||||
|
||||
dst = derived_mp4_path(photo_id)
|
||||
run_ffmpeg_web_transcode(photo.path, dst)
|
||||
photo.web_playable_path = str(dst)
|
||||
photo.web_transcode_status = "ready"
|
||||
photo.web_transcode_error = None
|
||||
db.commit()
|
||||
return {"ok": True, "transcoded": True}
|
||||
except Exception as e:
|
||||
try:
|
||||
db.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
print(f"[Task] web transcode failed for photo {photo_id}: {e}")
|
||||
traceback.print_exc()
|
||||
except (BrokenPipeError, OSError):
|
||||
pass
|
||||
try:
|
||||
photo = db.query(Photo).filter(Photo.id == photo_id).first()
|
||||
if photo:
|
||||
photo.web_transcode_status = "failed"
|
||||
photo.web_transcode_error = str(e)[:2000]
|
||||
db.commit()
|
||||
except Exception:
|
||||
pass
|
||||
return {"ok": False, "error": str(e)}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user