10 Commits
Author SHA1 Message Date
Claude 08ca5893c7 Fix IN/OUT marker placement: bližina trim handle namesto fiksnega initialCenter
PROBLEM: Ko je clip range premaknjen zelo daleč od LLM-jevega prvotnega
center-ja, klikni+Enter za zelen IN postavi rdeč OUT (in vice versa) —
ker se odločitev IN vs OUT temelji na 'initialCenter' (fiksen iz LLM
analize), ki ne predstavlja več trenutne uporabnikove pozicije.

Primer (Die Granaten — A Frau is a Frau):
- initialIn=0:50, initialOut=1:30 → initialCenter=1:10
- User razširi clip: trimStart=0:06, trimEnd=2:33
- User klikne pri 2:02, želi postaviti IN bližje začetku
- Stara koda: 2:02 > 1:10 → postavi OUT (NAROBE, ker je playhead daleč
  desno od dejanskega center-ja trim range-a)

FIX: Odločitev IN vs OUT po BLIŽINI playhead-a do trim handle-a:
- Bližje trimStart → IN marker
- Bližje trimEnd  → OUT marker

Plus: clamp markerjev znotraj trim range-a (vsaj 0.5s razlika med IN/OUT)
Plus: počisti staro initialCenter logiko v onPointerMove (handle drag)
2026-05-04 13:18:16 +00:00
Claude d2c9a48cc2 Google Sign-In auth + email whitelist + session cookies
WHY: Doslej basic auth z deljenim AUTH_USER/AUTH_PASS — tvegan za delitev
z drugimi (urednika ipd.). Z Google Sign-In dobi vsak uporabnik svoj
account, dostop pa nadzira whitelist v env.

NEW BACKEND:
- GET  /login              — login stran z Google Sign-In gumbom (GSI popup)
- POST /auth/google/callback  — verify Google ID token + set session cookie
- GET  /auth/me            — vrne email + auth method (za frontend header)
- GET  /logout             — pobriše cookie + redirect /login
- POST /logout

AUTH FLOW:
1. Browser GET /              → check_auth() → ni session → redirect /login
2. /login                     → Google Sign-In popup (preko gsi/client.js)
3. User izbere Google account → JS pošlje credential na /auth/google/callback
4. Server verifies token (oauth2.googleapis.com/tokeninfo)
5. Email mora biti v ALLOWED_EMAILS env
6. Set HttpOnly+Secure session cookie (HMAC-SHA256, 30 dni)
7. Redirect /

SECURITY:
- Session token: base64url(email|expiry|HMAC). Server ne ranji ne hrani.
- HMAC z SESSION_SECRET (auto-derived iz AUTH_USER+AUTH_PASS če ni nastavljen)
- Cookie HttpOnly + Secure + SameSite=lax
- Token verify: aud check, iss check, exp check, email_verified check

BACKWARD COMPAT:
- HTTPBasic auth še vedno deluje (cron, scripte, API klici)
- check_auth() probaj prvo cookie, potem basic
- Brez GOOGLE_CLIENT_ID env: vse še vedno dela na basic auth

ENV VARS (treba dodati v Coolify):
- GOOGLE_CLIENT_ID=938379241163-pvb328plec2207rbtufic8u5fgb6mkn9.apps.googleusercontent.com
- ALLOWED_EMAILS=sebastjan.artic@gmail.com,ales.cadez@gmail.com
- SESSION_SECRET=<random 64-char hex> (optional — defaultni se izračuna)

FRONTEND:
- Header: 👤 email + ↪ Odjava gumb (samo ko je prijavljen)
- DOMContentLoaded fetcha /auth/me, prikaže email
2026-05-04 12:26:53 +00:00
Claude 12e8edba93 Delete job: cascade delete povsod (Nextcloud + dedup DB + S3 + lokal)
PROBLEM: '✕' gumb je do zdaj brisal samo lokalne fajle + S3, ampak
NE Nextcloud upload (rel je ostal na folxspeed/REELS/{TV}/) in NE
dedup DB zapis (zato se enaka pesem ni mogla več upload-ati).

NEW BACKEND:
- _nextcloud_delete(filename, target_subdir) helper preko WebDAV DELETE
  (404 šteje kot success — če že ne obstaja, OK)
- delete_job() razširjen:
  1. Nextcloud delete (če nextcloud_status='uploaded' ali ima nextcloud_url)
  2. Dedup DB remove (processed_videos zapis za tisto TV postajo)
  3. Lokal + S3 delete vseh workfile-ov (kot prej)
  4. Glob za yt-dlp artifacte ({job_id}_yt*) — info.json, .part, .f137.mp4
  5. Job metadata
- Response: {deleted, nextcloud_delete: 'ok'|'not_found'|'fail: msg', nextcloud_filename}

NEW FRONTEND:
- buildJobEl() doda data-nc-status atribut na kartico
- deleteJob() bere dataset.ncStatus za Nextcloud info
- Confirm dialog detail razložen seznam KAJ se zbriše + opozorilo če Nextcloud
- Če Nextcloud delete ni uspel po API klicu, alert
2026-05-03 14:52:40 +00:00
Claude 79f611ba73 Retranscribe feature: ponovi STT z drugim providerjem v Edit modalu
PROBLEM: STT (Soniox/Scribe) včasih popolnoma narobe slišije besedilo —
npr. 'BILA JE LJUBEZEN PRVA' postane 'BILAL JO ME ZANPRLA' (TRIO ŠUBIC).
Edit modal je do zdaj zahteval popoln recut z novim clip range.

NEW: '🔁 Ponovi transkript' gumb v Edit modalu z dropdown za provider:
  - Scribe (ElevenLabs) — default, najboljši za nemščino + slovenščino
  - Soniox — slovenski default v auto routing
  - Whisper local (faster-whisper)

Backend:
  POST /api/jobs/{id}/retranscribe { provider, auto_upload?, lang? }
  - Briše stari MP4 + analysis.json + .srt + .ass (lokal + S3)
  - Re-queue job z whisper_provider override
  - Ohrani isti clip range — analyze.py si naredi svežo analizo
  - retranscribe_count števec za sledenje poskusov

UX: confirm dialog → 1.5s status → auto-close modal → watchJob za live progress.
Brez auto_upload v Nextcloud — preveri rezultat preden re-uploadaš.
2026-05-03 14:38:35 +00:00
Claude 2abd9daae1 Fix CP1250 encoding bug v sync_qnet.py — È→Č
PROBLEM: Songs.txt na MB Windows playerjih je v CP1250 (slovenski/CEE),
NE Windows-1252 (Western European). iconv -f WINDOWS-1252 je 'Č' (0xC8)
napačno interpretiral kot 'È', zaradi česar je 811 zapisov v Qnet bazi
imelo 'È' namesto 'Č' (npr. 'POSKOÈNI', 'ÈAS ZA ZABAVO', 'STORŽIÈ').

Posledica: ko je qnet_match povezal job na napačno labeliran zapis,
je 'parsed_title' polnil z mojibake iz Qnet baze (15 jobov).

FIX: WINDOWS-1252 → WINDOWS-1250.
Razlike v CP1250 vs CP1252 (slovanske črke):
  Č↔È, č↔è, Ć↔Æ, ć↔æ, Đ↔Ð, đ↔ð, Ń↔Ñ, Ł↔£, ł↔³, Ś↔Œ, ś↔œ, ź↔Ÿ
  Ž, š, ž — ostanejo (isti byte v obeh)

BACKFILL (ločen skript, že apliciran):
- Qnet lookup: 2746 polj v 20860 zapisih popravljenih
- Qnet songs.json: 2856 polj
- 15 jobov: parsed_artist/title popravljen na pravilen UTF-8
2026-05-03 14:32:25 +00:00
Claude 576cc807b5 Fix parse_artist_title (ANS.* bug) + unify station naming na FOLX SLO
PROBLEMS:
1. parse_artist_title je uporabljal Path(s).stem za stripping ext-a,
   kar je pri YT title 'ANS.NAVEZA - SREČA OPOTEČA' vrnilo 'ANS' (Path
   smatra '.NAVEZA - SREČA OPOTEČA' kot extension). Posledica: parser
   failed → ACR fallback → 'Folx' kot artist na 15+ jobih.

2. tv_station je imel dva imena: 'FOLX SLOVENIJA' (frontend default)
   in 'FOLX SLO' (qnet match output) — UI tabi niso seštevali pravilno.

FIXES:
- parse_artist_title: ext stripping samo za znane ekstenzije
  (.mp4, .mp3, .m4a, .webm, .mkv, .avi, .mov, .wav, .flac, .aac,
   .opus, .ogg, .wmv, .mxf), NE za naključne pike v YT title.
- Vsi defaultni 'FOLX SLOVENIJA' → 'FOLX SLO' v Pydantic modelih +
  templates + filter tabi.
- Nextcloud mapping STATION_TO_NEXTCLOUD_FOLDER ostane nespremenjen
  (FOLX SLO → mapa 'FOLX SLOVENIJA', kjer pač zaplane).

BACKFILL (že apliciran prek scripte):
- 15 jobov z parsed_artist='Folx' popravljenih na pravi izvajalec
  iz youtube_title (ANS.NAVEZA, ANS.BITENC, ANS. ROKA ŽLINDRE, ipd.).
- 86 jobov tv_station 'FOLX SLOVENIJA' → 'FOLX SLO'.
2026-05-03 14:26:42 +00:00
Claude cd872d8bea Layout: razširi main na 1600px + levi panel fiksno 440px
Prej: max-width 1100px, 1fr/1fr grid → na velikih ekranih je bil
levi 'Nov reel' panel zelo otesnjen, desni list pa premajhen.

Zdaj:
- main max-width 1600px (več prostora na velikih ekranih)
- levi panel fiksno 440px (vsebina diha + tabi v eni vrstici)
- desni jobs list zavzema vso preostalo širino (1fr)
- Responsive: <1100px → levi 380px, <900px → stack 1 stolpec
2026-05-03 14:15:42 +00:00
Claude 2fcc4b8075 Dashboard: TV station filter tabs nad jobs-list
- Tabi: Vse / FOLX SLOVENIJA / FOLX DE / ONE DE / ZWEI MUSIC / ADRIA / (brez postaje)
- Števec na vsakem tabu = število NEPOTRJENIH jobov (!hidden_after_upload) za to postajo
- Klik na tab = filter samo tisti station, samo nepotrjeni
- 'Vse' = vsi nepotrjeni (kot do zdaj)
- '(brez postaje)' tab se skrije, če ni jobov brez postaje
- Persist v localStorage (reels_jobs_station_filter)
- Iskanje IGNORIRA station filter (vrne tudi naložene + vse postaje)
- Empty state sporočilo prilagojeno glede na izbran filter
2026-05-03 12:48:31 +00:00
Claude 0d72d70f5d S3 mirror integration: workfiles auto-mirror to s3://folxspeed/reels-app/
- main.py: 4 helper funcs (_persist_to_s3, _ensure_local, _delete_from_s3,
  _ffmpeg_then_persist) - no-op fallback when S3 creds missing
- save_job(): mirror metadata JSON to S3
- process_job(): mirror YT download + render output + analysis/srt/ass to S3
- upload_video(): mirror direct uploads to S3
- _precache_edit_assets(): Popen->threaded with S3 sync after ffmpeg
- read endpoints (download, preview, source_video, waveform, preview_clip,
  get_transcript, recut_job): _ensure_local() fallback fetch from S3
- delete_job(): cascade delete to S3 (mirror unlink)
- cleanup.py: NEW module, deletes local files >48h that exist in S3.
  Verified by S3 head_object + size match. NOT YET ACTIVATED in cron.

Backward compat: lokalna mapa ostane primary. Brez env vars S3_* vsi
helperji vrnejo False (no-op). Production behavior identičen, dokler
ne dobi S3 creds.
2026-05-03 12:24:18 +00:00
Claude ec1d109e3b S3 storage module: boto3 abstraction for reels-app workfiles (uploads/outputs/jobs prefixes) 2026-05-03 11:57:12 +00:00