Edit: instant client-side preview with low-q source
User feedback: 'predvaja odsek in začne iz nule kar ni ok, ne moremo
premikati levo dolj levo... za to bi rabili low-q?'
REPLACED render-on-demand approach with low-q source download:
1. Backend: GET /api/source-video/{id}?quality=low
- 480p re-encode of full source (cached after first request)
- veryfast preset, CRF 28
- First request: ~5-10s (depends on song length)
- Subsequent: instant (cached)
2. Frontend: Edit modal loads ?quality=low
- 'Pripravljam predogled (~5s prvič, potem instant)' status
- Once loaded: ALL preview is client-side instant
- 'Predvajaj odsek' jumps to trimStart and plays
- Auto-stop at trimEnd (loops back)
- Drag handles DURING playback = instant seek (browser scrubs in 5MB)
- Drag NOT blocked during play (you can fine-tune in/out live)
3. Removed old /api/preview-clip endpoint logic (no longer needed)
Note: kept the route as cache cleanup for old jobs
Workflow now:
- Open Edit → 5s wait first time
- Drag handles freely (instant scrubbing)
- Click Predvajaj → starts at trimStart immediately
- Drag handles WHILE playing → live preview
- Save when satisfied → 70s full render
This commit is contained in:
+46
-2
@@ -1233,14 +1233,58 @@ async def delete_job(job_id: str, user: str = Depends(check_auth)):
|
||||
|
||||
# ─── EDIT FEATURE ────────────────────────────────────────────────
|
||||
@app.get("/api/source-video/{job_id}")
|
||||
async def source_video(job_id: str, user: str = Depends(check_auth)):
|
||||
"""Vrne 16:9 original video za predogled v editorju."""
|
||||
async def source_video(job_id: str, quality: str = "high", user: str = Depends(check_auth)):
|
||||
"""Vrne 16:9 original video za predogled v editorju.
|
||||
|
||||
quality='high' (default): originalni file
|
||||
quality='low': 480p re-encoded version (cached) — za hitro scrubbanje v editorju
|
||||
"""
|
||||
job = load_job(job_id)
|
||||
if not job:
|
||||
raise HTTPException(404, "Ne obstaja")
|
||||
src = job.get("input_path")
|
||||
if not src or not Path(src).exists():
|
||||
raise HTTPException(404, "Original video ne obstaja")
|
||||
|
||||
if quality == "low":
|
||||
# 480p cached za hitro scrubbanje
|
||||
cache_path = OUTPUT_DIR / f"{job_id}_source_low.mp4"
|
||||
cache_valid = cache_path.exists() and cache_path.stat().st_size > 1024
|
||||
|
||||
if not cache_valid:
|
||||
if cache_path.exists():
|
||||
cache_path.unlink()
|
||||
cmd = [
|
||||
"ffmpeg", "-y",
|
||||
"-i", str(src),
|
||||
"-vf", "scale=854:480:force_original_aspect_ratio=decrease,scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-c:v", "libx264",
|
||||
"-preset", "veryfast", # malo boljša kvaliteta od ultrafast
|
||||
"-crf", "28",
|
||||
"-c:a", "aac",
|
||||
"-b:a", "96k",
|
||||
"-movflags", "+faststart",
|
||||
"-loglevel", "error",
|
||||
str(cache_path),
|
||||
]
|
||||
try:
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||
if proc.returncode != 0 or not cache_path.exists() or cache_path.stat().st_size < 1024:
|
||||
if cache_path.exists():
|
||||
cache_path.unlink()
|
||||
raise HTTPException(500, f"FFmpeg failed: {(proc.stderr or 'unknown')[-300:]}")
|
||||
except subprocess.TimeoutExpired:
|
||||
if cache_path.exists():
|
||||
cache_path.unlink()
|
||||
raise HTTPException(500, "Low-q source render timeout (>120s)")
|
||||
|
||||
return FileResponse(
|
||||
path=cache_path,
|
||||
media_type="video/mp4",
|
||||
filename=f"source_low_{job_id}.mp4",
|
||||
headers={"Accept-Ranges": "bytes", "Cache-Control": "max-age=3600"},
|
||||
)
|
||||
|
||||
return FileResponse(
|
||||
path=src,
|
||||
media_type="video/mp4",
|
||||
|
||||
Reference in New Issue
Block a user