Multi-upload batch queue + Telegram notifications

Changes:

1. Frontend multi-upload:
   - File input now has 'multiple' attribute, drag-drop accepts multiple
   - File queue list with per-file artist/title preview + remove button
   - 'Pošlji vse' uploads sequentially (one at a time to avoid network saturation)
   - Each file gets same batch_id for Telegram batch summary
   - After upload, queue clears, jobs appear in right sidebar

2. Backend queue worker:
   - New _queue_worker() background thread processes 'queued' jobs sequentially
   - Only 1 job at a time to keep openclaw stable (avoid CPU/RAM thrash)
   - FIFO order by created_at
   - Auto-starts on app startup after job resume

3. Job submission flow change:
   - /api/process and /api/youtube no longer call background.add_task directly
   - Just mark status='queued', queue worker picks up
   - This means upload completes fast, processing happens in background
   - User can close browser, jobs continue

4. Telegram notifications (FOLX Alerts bot):
   - Per-job: 'Reel pripravljen: Lady Gaga - Abracadabra (29s, 30 MB)'
   - Per-job failed: 'Reel ni uspel: <name> + error message'
   - Batch summary: 'Batch končan: 10/10 reels pripravljeni' (only if >1 in batch)
   - Uses existing TELEGRAM_TOKEN + TELEGRAM_CHAT_ID env vars
   - app/telegram.py module with notify_job_done(), notify_job_failed(),
     notify_batch_complete()

5. batch_id field:
   - Added to Job model + StartJobIn pydantic
   - Saved during upload + process
   - Used to count batch progress and trigger summary notification

User experience:
- Drag 20 videos at once
- Click 'Pošlji'
- Close browser, go grab coffee
- Telegram sends 'Reel pripravljen' for each
- After all done: 'Batch končan: 20/20 reels pripravljeni' summary
- Open app to download all
This commit is contained in:
2026-04-29 15:12:38 +00:00
parent 157e6b781e
commit 91cc03658d
4 changed files with 426 additions and 93 deletions
+42 -14
View File
@@ -1429,31 +1429,59 @@ def main():
clip_range["reason"] += f" (start extended back)"
# Najdi vse segmente ki se začnejo PO trenutnem clip end
# STROŽJA pravila: ne podaljšuj v naslednji refren / verz / instrumental.
# Razširjamo SAMO če zadnji segment se prekriva s clip (klesti iz njega) ALI
# če je naslednji segment KRATEK (< 2s) IN vsebuje samo outro fillerje
# (la la, oh, yeah, ej, ja, ah, na, hey itd.).
# Definiraj outro filler regex (multi-jezikovno)
import re as _re
OUTRO_FILLER_RE = _re.compile(
r'^[\s\-,.!?]*'
r'((?:la|na|oh|ah|eh|ej|aj|ja|hey|yeah|yo|ho|wo|hu|mm|nn|uu|oo|aa|ee|ii)'
r'[\s\-,.!?]*)+'
r'[\s\-,.!?]*$',
_re.IGNORECASE
)
# Hard cap: ne razširjaj več kot 3s nad původne clip end
original_clip_end = clip_range["end"]
soft_extension_limit = min(original_clip_end + 3.0, extension_limit)
for seg in corrected_segs:
seg_start = float(seg.get("start", 0))
seg_end = float(seg.get("end", 0))
# Segment začnemo po trenutnem clip end
seg_text = seg.get("text", "").strip()
# Segment se prekriva s clip end (zadnji segment refrena, ki ni zaključen)
if seg_start <= current_end:
# Segment se prekriva s clip — če konča pred extension_limit, podaljšaj
if seg_end > current_end and seg_end <= extension_limit:
# Podaljšaj clip do konca tega segmenta + 0.3s diha
new_end = min(seg_end + 0.3, extension_limit)
if seg_end > current_end and seg_end <= soft_extension_limit:
new_end = min(seg_end + 0.3, soft_extension_limit)
if new_end > current_end:
print(f" 🎵 Podaljšam clip {current_end:.1f}s → {new_end:.1f}s "
f"(zadnji segment refrena se zaključi)", file=sys.stderr)
current_end = new_end
else:
# Segment je popolnoma za clip end — preverim ali je v dosegu in ali nima dolge pavze pred njim
# Segment začne PO clip end — preveri ali je outro filler
pause = seg_start - current_end
if pause < 1.0 and seg_end <= extension_limit:
# Še vedno povezano s clipom (kratko pavzo, naprej outro/echo)
new_end = min(seg_end + 0.3, extension_limit)
if new_end > current_end:
print(f" 🎵 Podaljšam clip {current_end:.1f}s → {new_end:.1f}s "
f"(outro segment z {pause:.1f}s pavzo)", file=sys.stderr)
current_end = new_end
# Predaleč → ustavi se
if pause >= 0.7:
break
# Predolg segment = nov verz/refren, ne dodaj
if (seg_end - seg_start) > 2.5:
break
# Preveri vsebino — če ni samo outro fillerji, NE dodaj
if not OUTRO_FILLER_RE.match(seg_text):
# Ni filler → verjetno nov refren/verz/post-chorus
break
# OK, je outro filler — dodaj
new_end = min(seg_end + 0.2, soft_extension_limit)
if new_end > current_end:
print(f" 🎵 Podaljšam clip {current_end:.1f}s → {new_end:.1f}s "
f"(outro filler '{seg_text[:40]}')", file=sys.stderr)
current_end = new_end
else:
# Daljša pavza — ustavi se tu
break
if current_end > clip_range["end"]: