Commit Graph

144 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
OpenClaw Agent
48bf0cf050 UI: Nextcloud gumb \u2014 optimistic feedback. Takoj ob kliku gumb postane oran\u017een "\u23f3 Po\u0161iljam...", disabled, cursor:wait. Po uspe\u0161nem upload-u refreshJobs() zamenja v zelen \u2713 Nextcloud. Pri napaki vrne original state. 2026-05-03 12:27:24 +02:00
OpenClaw Agent
6a9e20da19 Nextcloud upload: mapping Qnet station -> NC folder. Qnet baza ima FOLX SLO/ONE/ZWEI, Nextcloud pa FOLX SLOVENIJA/ONE DE/ZWEI MUSIC \u2014 STATION_TO_NEXTCLOUD_FOLDER dict normalizira pri uploadu. Re\u0161i HTTP 404 NotFound za FOLX SLO jobe (106 jobov). 2026-05-03 12:24:35 +02:00
OpenClaw Agent
a960b7157f UI: dashboard prikazuje Artist - Title (parsed_artist + parsed_title) namesto YT URL. Fallback chain: parsed -> youtube_title -> youtube_url -> filename -> id. Ker commit 968eba7 \u017ee shrani vse YT metapodatke in Qnet match na single video submit, dashboard zdaj prikazuje \u010cisti Artist - Title za vse jobe. 2026-05-03 11:50:45 +02:00
OpenClaw Agent
968eba7205 YT metadata fetch: razširi --info-only output (id, uploader, description, upload_date, view_count, tags, ...). Single video submit fetcha metadata + Qnet match takoj (kot playlist). Worker preskoči info fetch če metadata že obstaja, sicer shrani vsa polja in naredi Qnet match.
- yt_download.py: get_info() probaj najprej yt.biba.live API /download/info (residential IP, sveži cookies), fallback na lokalni yt-dlp. --info-only output razširjen na 17 polj.
- main.py submit_youtube single video: fetcha metadata (yt_get_info) ob submit, shrani youtube_title/uploader/id/description/duration/thumbnail/upload_date in naredi Qnet match (parity s playlist branch).
- main.py worker: skip info fetch če youtube_title in youtube_uploader že obstajata. Sicer shrani VSE polja + Qnet match + parser fallback.
2026-05-02 15:54:28 +00:00
OpenClaw Agent
bc73fd8dd3 Reels-app: integracija z yt.biba.live API. get_cookies_file fetcha sve\u017ee cookies iz /cookies/raw (5 min cache), get_playlist najprej probava /download/playlist API, fallback na lokalni yt-dlp. ENV: YT_API_URL, YT_API_TOKEN. 2026-05-02 13:17:30 +00:00
OpenClaw Agent
77075795ce YouTube playlist support: /api/youtube/playlist-preview za pred-confirm, /api/youtube zaznava 'list=' URL in kreira batch (1 job/video). Qnet auto-match na YT naslovu, Confirm dialog v UI z prvih 5 naslovov. 2026-05-02 12:34:27 +00:00
OpenClaw Agent
7a7d7ea20d Preview cache-busting: Cache-Control: no-cache na endpoint + ?v=Date.now() v frontend URL — browser ne sme cachat starega output-a po recutu 2026-05-02 12:24:19 +00:00
OpenClaw Agent
c1e00b7b73 Final SAR=1 fix: dodaj setsar=1 na konec vfilter-ja v reframe.py + ass filtrom v subtitle.py (kompenzira rounding errore iz scale/crop filtrov, ki dajo SAR 10240:10239 namesto 1:1) 2026-05-02 12:12:38 +00:00
OpenClaw Agent
6279b0ec03 subtitle.py: dodaj -pix_fmt yuv420p v burn-in encode (subtitle re-encode je perpetuiral broadcast yuv422p iz prej\u0161nje stopnje) 2026-05-02 12:05:47 +00:00
OpenClaw Agent
46ec0cec9c Anamorphic source fix: pred-scale na square pixel (PAL DV 720x576 SAR 64:45 \u2192 1024x576) preden track/center/blur filter dela. Brez tega so se reels iz starih MPG-2 (npr. Global Kryner Like a Virgin) razteg-nili horizontalno. 2026-05-02 11:57:09 +00:00
OpenClaw Agent
0fe1a47295 Fix output pixel format: -pix_fmt yuv420p + setsar=1 v vseh ffmpeg ukazih (broadcast .MPG iz Qnet je yuv422p z anamorphic SAR 64:45 \u2192 popa\u010den DAR 4:5 namesto 9:16, ne kompatibilen z Instagram/FB/mobile playerji) 2026-05-02 11:42:08 +00:00
OpenClaw Agent
6270c92b44 STT routing: FOLX DE / ZWEI \u2192 Scribe default (4\u00d7 hitrej\u0161e + brez Mississippi/Mrs. Sadie halucinacij). SLO postaje ostanejo na Soniox. User lahko override v UI. 2026-05-02 11:17:51 +00:00
OpenClaw Agent
1f8565413a Qnet match VEDNO shranjen v job.qnet_match (audit + tv_station auto-fill), tudi ko client po\u0161lje artist+title 2026-05-02 10:57:09 +00:00
OpenClaw Agent
24e1b53aa8 Qnet match v upload queue — auto-prepoznavanje pesmi takoj ko izbere\u0161 fajle (parallel POST /api/qnet/match-batch). \u010ce baza prepozna komad, prikaz Artist \u2014 Title z station badge namesto 'Brez razvidnega imena'. 2026-05-02 10:47:51 +00:00
OpenClaw Agent
b938d1e4d8 Qnet song match — fetcha Songs.txt iz 5 MB playerjev (FOLX DE/SLO, ZWEI, ONE, ADRIA), 20K+ songs, fuzzy match na upload-u → clean parsed_artist/parsed_title + auto tv_station. /api/qnet/{stats,match,sync} 2026-05-02 10:42:35 +00:00
OpenClaw Agent
6f79aaea8d Iskalnik v 'moji reels' (artist+title+filename, debounced 150ms) 2026-05-02 10:02:21 +00:00
4febf0b844 Trailing bare 4K/HD/8K (brez oklepajev)
Edge case: 'Naslov (Official video) 4K.mxf'
Pred: '(Official video)' odstranjen, ampak '4K' brez oklepajev ostane

Pattern: r'\b(?:4K|HD|HQ|8K|1080p|720p)\s*$'
- Samo na koncu stringa
- Ne v sredini (Top 100 hitov ohranjen)
2026-05-02 08:55:44 +00:00
2aec7f7a29 Odstrani trailing 2-4 cifrene številke (leto/verzija)
User: 'včasih številka 23 pri Modrijanih in pri Firbcih, te se pojavlajo
v naslovih'

Razlog: NAS interno označeni kot leto produkcije.
'PA KAJ (Official Video) 23' = leto 2023
'ONA HOČE (Official video) 22' = leto 2022
'S tabo res rad (Official Video) 33' = leto 2033 (?) ali interno

Pattern: r'\s+\d{2,4}\s*$' — trailing 2-4 cifrena številka

Test rezultati:
- 'S tabo res rad 33' → 'S tabo res rad' 
- 'PA KAJ 23' → 'PA KAJ' 
- 'PESEM GORENJSKIH TRAT (2023)' → 'PESEM GORENJSKIH TRAT' 

Edge cases (NI odstranjeno):
- 'Pesem 25 ljubljanskih ulic' (število v sredini) 
- 'Top 100 hitov' 
2026-05-02 08:52:40 +00:00
4e2c690bc5 Bolj agresivno čiščenje filename: () prazni + catch-all noise besede
User: 'Topliška pomlad — KAR PADA NAJ SNEG ( - ) — tile oklepaji
pa Official video itd. Daj ko se nalaga na Nextcloud mora biti samo
izvajalec in naslov komada.'

Dodatni NOISE_PATTERNS:
1. Prazni / dummy oklepaji: '( )', '( - )', '(-)', '(.)' itd.
2. Catch-all za oklepaje z noise besedami:
   video|audio|version|mix|edit|remix|cover|live|hd|hq|4k|8k|
   remaster|extended|clean|explicit|radio|lyric|official|musik
3. Avtor/producer brackets: '(prod. by X)', '(feat. Y)', '(ft. Z)'

Test rezultat:
'Topliška pomlad - KAR PADA NAJ SNEG ( - )(Official 4K Video).mp4'
→ 'Topliška pomlad - KAR PADA NAJ SNEG - REEL.mp4'

'Sarah Connor - FICKA (Offizielles Musikvideo).mp4'
→ 'Sarah Connor - FICKA - REEL.mp4'

Vsi novi uploadi bodo imeli čista imena.
TODO ločeno: rename obstoječih 31 datotek na Nextcloudu (skript pripravljen)
2026-05-02 08:10:30 +00:00
376bb4db09 Manual Nextcloud upload tudi nastavi hidden_after_upload=True
User: 'naredi da ko damo IN sejvamo na nextcloud da izgine iz pregleda
in se vrne če stisnemo Pokaži že naložene'

Bug: 2 mesti uploadamo na Nextcloud:
1. Avto-upload po recut/Save → hidden_after_upload=True 
2. Manual ☁ Nextcloud klik → samo nextcloud_status='uploaded' 

Fix: oba puta nastavi hidden_after_upload=True

Tudi: batch fix obstoječih 19 uploaded jobs ki niso imeli hidden flag,
posredno preko docker exec — zdaj se skrijejo tudi oni.

Workflow zdaj:
- ☁ Nextcloud klik → upload + hidden=true → izgine
- Save (Edit) → re-render + auto-upload + hidden=true → izgine
- ☐→☑ 'Pokaži tudi že naložene' → vidiš vse z zelenim borderjem
2026-05-02 08:01:37 +00:00
f5ef136bf4 Marker center = ORIGINAL clip center (not dynamic)
User: 'če razširim zoom in podaljšam komad potem polovica ni več tam
kjer je reel IN in tam kjer je OUT. Naj se program drži polovice
avtomatično narejenega reela'

Pred (bug):
  center = (trimStart + trimEnd) / 2  # dynamic
  Ko user razširi clip 60-90 → 50-110:
    nov center = 80 (prej 75)
    Klik pri 78s → IN (čeprav user mislil OUT!)

Po (fix):
  initialCenter = (startInit + endInit) / 2  # FIKSEN, izračunan ob open
  Original LLM center se NE spreminja
  Klik pri 78s vedno → OUT (če je 78 > 75)
  Klik pri 70s vedno → IN

Ne glede koliko user razširi/oža clip, marker assignment uporablja
fiksno 'sredino' originalnega LLM-jevega clipa.

Tudi drag handle: če marker zaide čez initialCenter → reset na trim border.
2026-05-02 07:53:44 +00:00
40151f8f57 STROGI marker boundaries: zelen samo levo od center, rdeč samo desno
User: 'še vedno mi skače zeleni trikotnik na konec! Naredi da ne gre
čez polovico ne en ne drug'

Bug analiza: prej Math.min(t, center - 0.1) NI bilo, samo direktna
assignment. Če bi user nekje napačno klikal, ali če je drag handle-a
spremenil center, marker je pristal na napačni strani.

Strogo zdaj:
- markerInTime = Math.min(t, center - 0.1)  → zelen NIKOLI čez center
- markerOutTime = Math.max(t, center + 0.1) → rdeč NIKOLI čez center

Plus: po drag-u handle-ja se markerji preverijo:
- Če je markerIn na desni strani novega centra → reset na trimStart
- Če je markerOut na levi strani novega centra → reset na trimEnd

Plus console.log za debug — vidiš v Dev Tools kateri vrednosti uporabljam.
2026-05-02 07:26:54 +00:00
bdc1d14498 Toggle 'Pokaži tudi že naložene' + recut prepiše Nextcloud
User feedback: 'načeloma bi se moral samo če kaj spregledamo in že
sejvamo pa ne moremo nazaj vrniti in popraviti'

Frontend:
- Nov toggle '☁ Pokaži tudi že naložene' nad 'moji reels' headerjem
- Default OFF: vidiš samo aktivne (ki niso uploaded)
- ON: vidiš VSE, vključno z uploaded (z zelenim borderjem)
- buildJobEl: uploaded reels imajo border-left zelen + bg #4ade80 0.04 opacity
- Listener: change event sproži refreshJobs()

Backend:
- recut endpoint: reset hidden_after_upload=false, nextcloud_status='recutting'
- Po končanem recut: avto-upload na Nextcloud (PUT prepiše obstoječi file)
- Hidden_after_upload=true spet po uspešnem re-uploadu

Workflow:
1. Reel uploaded → hidden, ni v UI
2. Klik toggle 'Pokaži tudi že naložene' → vidiš ga (zelen border)
3. Edit → Save → re-render (visible spet, status='processing')
4. Re-upload PUT na Nextcloud → prepiše obstoječi file (ista pot, isto ime)
5. Hidden=true → izgine spet (osim če toggle on)
2026-05-02 07:23:03 +00:00
129f7d5f33 Marker assignment: split po sredini clipa (ne po bližini handle-a)
User feedback: 'če kliknemo na konec se premika IN, in ne OUT.
Če je bližje OUT ko kliknemo, se mora premikati OUT trikotnik —
tudi če nismo prej izbrali IN. Če je IN ok potem tega ne delamo.'

Pred (bug):
  distToLeft = |t - trimStart|
  distToRight = |t - trimEnd|
  Če enaka razdalja → IN (default '<=')
  Ko si že popravil IN, distance lahko enaka → klik blizu OUT
  pomotoma premakne IN

Po:
  center = (trimStart + trimEnd) / 2
  if t < center → IN
  else → OUT

Logika: leva polovica clipa = IN domain, desna = OUT domain.
Klik kjerkoli blizu desnega handle (= konec) bo VEDNO premaknil OUT.
Klik kjerkoli blizu levega handle (= začetek) bo VEDNO premaknil IN.
Tudi če si že popravil IN, popravljanje OUT ne bo motilo IN markerja.
2026-05-02 07:02:05 +00:00
c58875c072 Upload: timeout + retry + ne ustavi loop ob enem fail-u
User: 'zakaj se je ustavilo? Naložil sem več kot 70.'

Diagnoza: 12/70+ je prišlo do server-ja (vsi 200 OK).
Browser-side problem: en upload je stuck → cel for-loop blokiran.

Fixes:
1. xhr.timeout = 10min per file (prej: večnost)
2. xhr.ontimeout, xhr.upload.ontimeout — proper error handling
3. NEW: uploadFileWithRetry() — 2x retry z 2s/4s eksponentnim delay-om
   za očasne mrežne odpovedi
4. Catch v loop ne kliče liveFail() (kar bi naredil disabled submit-btn)
   ampak samo showLive() z 'preskočil' sporočilo → loop nadaljuje
5. Console.warn v retry attempts za debugging

Sedaj če eno failes:
- Retry 2x avtomatsko (2s + 4s delay)
- Če še vedno ne uspe → preskoči to datoteko, nadaljuj z naslednjo
- Pri koncu vidiš katere so failed (v console + showLive)
2026-04-30 16:28:27 +00:00
2de58ca7a5 Skrij vse settings razen no-subs — vse drugo zapečen preset
User: 'sej je že preset narejen, ne kompliciraj. Vse je enako za reels.
Naredi da so podnapisi po default GOR (brez kljukice). Klukica = brez napisov.
Ostalo zapeči, nimamo kaj spreminjat.'

Hidden defaults (zapečen preset za vse reels):
- mode=track
- quality=medium
- llm-provider=claude
- whisper-model=large-v3
- subtitle-style=reels
- auto-chorus=true (data-checked)
- include-prebuild=false (data-checked)
- duration=30

Vidno samo:
- TV postaja (tabs) — kam gre na Nextcloud
- Brez kljukice = napisi V VIDEO (default)
- Kljukica = brez napisov

Side note: 'Brez kljukice = napisi' je obratno od prej (kljukica = brez).
Default je sedaj 'with subtitles' (no-subs unchecked). Persistence v
localStorage ohranja izbiro med reload-i.

Bonus: odstranjen 'Auto-chorus toggle' handler ki je iskal #manual-times
ki ne obstaja več.
2026-04-30 15:20:52 +00:00
d0e78cca02 Persist UI settings v localStorage (no-subs, station, mode, ...)
User feedback: 'ko refrešam se ponovno vklopi kljukica da ni
podnapisov to bo problem'

Saved fields:
- no-subs (checkbox)
- auto-chorus (checkbox)
- include-prebuild (checkbox)
- mode (track/center/blur)
- quality (fast/medium/high)
- llm-provider (claude/gemini/auto)
- tv-station (FOLX SLOVENIJA / ONE DE / ...)

On page load: re-aplicira saved values
On change: shrani v localStorage
TV station: tudi posodobi aktiven tab style

Defaults ostanejo isti če nikoli niso spremenjeni.
2026-04-30 15:17:16 +00:00
c2a593de78 Fix JS syntax error: duplicate q.appendChild block
Bug: previous str_replace introduced duplicate '});' lines after
file queue rendering. Caused 'Unexpected token )' parse error,
which broke ALL JS — addFilesToQueue was undefined → upload didn't work.

User feedback: 'ne dela upload'

Verified with: node -c on extracted script — passes now.
2026-04-30 15:14:09 +00:00
b9c8c066ec Odstrani emoji iz TV postaj + cleanup all reels
User feedback: 'pa ne maram teh emojijev ki si ji dal k Adria in ZWEI Music.
Daj počisti sedaj kar je že narejeno in gremnalagat.'

UI changes:
- Tab labels: 'FOLX SLOVENIJA', 'FOLX DE', 'ONE DE', 'ZWEI MUSIC', 'ADRIA'
  (without emoji prefixes)
- Job card badge: brez emoji prefix

Cleanup (manual via container):
- /data/jobs/*.json (15 → 0)
- /data/outputs/* (132 → 0)
- /data/uploads/* (15 → 0)

Dedup baza je ohranjena (15 zapisov), tako da če uporabnik poskuša
naložiti komad ki je že naložen na Nextcloud, dobi opozorilo.
2026-04-30 15:10:28 +00:00
5f339e91b5 Bolj jasen tekst: 'Izklopi podnapise' s pojasnilom
User feedback: 'ko ni kljukice so napisi, zato napiši samo izklopi
podnapise in ko je kljukica so izklopljeni'

Pred: 'Brez podnapisov (privzeto — bolj zanesljivo)'
Po:   'Izklopi podnapise (kljukica = brez napisov · brez kljukice = napisi v video)'

Logika ostane ista (no_subs flag), samo label je bolj jasen.
2026-04-30 15:05:22 +00:00
f2034f9970 Dedup: SQLite baza za že obdelane komade
User feedback: 'dodaj da če čekira in shranjuje že obdelani komadi v SQL bazo,
da če nalagamo komad ki smo ga že naložili da ga ne naloži'

NEW: SQLite dedup database at /data/processed.db
Schema: processed_videos
  - normalized_name (PK part 1)
  - tv_station (PK part 2) — isti komad lahko obstaja na različnih postajah
  - filename_orig
  - job_id
  - nextcloud_url
  - file_size_mb
  - uploaded_at

Filename normalization removes noise:
  'BRAJDE (Official Video).mp4' → 'brajde'
  'Brajde (HD).mxf' → 'brajde'
  'BRAJDE - LIVE 2024.mp4' → 'brajde'
(strips parentheses, suffixes like Official/HD/4K/Live, extension, lowercase)

NEW endpoints:
- POST /api/dedup/check — preveri katera imena so že obdelana
- POST /api/dedup/remove — pobriše dedup zapis (Re-process)
- GET /api/dedup/list — seznam vseh obdelanih (opt. filter po tv_station)

Integration:
- Nextcloud upload (manual + auto): zabeleži v dedup po uspešnem PUT
- File queue (frontend): pred dodajanjem preveri dedup
  → prikaže rdeč warning '⚠ Že naložen na ONE DE (29.4.2026) — Re-process'
  → opacity 0.6 (vizualno blediji)
  → submit jih SKIP-a (osim če 'Re-process' kliknil)
2026-04-30 15:00:10 +00:00
16c332b490 Save → avto-upload v Nextcloud → reel izgine iz seznama
User feedback: 'zaenkrat bomo ročno popravljali in pregledovali. Ko kliknemo
Save potem se shrani v pravi folder in izgine.'

Workflow:
1. User izbere TV postajo (zavihek)
2. Naloži komade
3. Reel se renderira (auto chorus)
4. User pregleda + Edit če treba
5. Save → re-render z user popravki
6. Po končanem re-render: AVTO-upload v Nextcloud /folxspeed/REELS/{station}/
7. Reel IZGINE iz seznama (hidden_after_upload flag)

Backend changes:
- RecutRequest: nov field auto_upload (default True)
- update_job: shrani auto_upload_to_nextcloud
- process_job done block: če flag set + Nextcloud configured →
  upload + nextcloud_status='uploaded' + hidden_after_upload=True

Frontend changes:
- refreshJobs: filter out jobs with hidden_after_upload
- TV station badge na vsaki kartici (z emoji + ime postaje)
- Vidiš na prvi pogled kam bo šlo

Workflow rezultat: po Save reel izgine, je avtomatsko v pravi mapi
2026-04-30 14:53:01 +00:00
1c11dfe630 TV station tabs + per-station Nextcloud upload target
User feedback: 'Sarah Connor in Abracadabra grejo v ONE DE, ne v FOLX SLO.
Naredi zavihke za vsako TV postajo (FOLX SLO, FOLX DE, ONE DE, ZWEI MUSIC, ADRIA)
in upload gre v ustrezno Nextcloud podmapo.'

Backend changes:
- StartJobIn + YouTubeJobIn: nov field 'tv_station' (default 'FOLX SLOVENIJA')
- update_job: shrani tv_station v job JSON
- POST /api/jobs/{id}/upload-nextcloud:
  bere tv_station iz job, target_subdir = folxspeed/REELS/{station}

Frontend changes:
- 5 TV station tabs: FOLX SLOVENIJA (active), FOLX DE, ONE DE, ZWEI MUSIC, ADRIA
- Hidden input #tv-station-input drži current selection
- Klik na tab ga aktivira (accent color)
- collectSettings() vključuje tv_station

Manual fix: Sarah Connor in Abracadabra job.json popravljena → tv_station=ONE DE
Reset njihovih nextcloud_* polj da bo upload v pravo mapo.
2026-04-30 14:38:45 +00:00
8284181fb3 Cleanup: odstrani duplikat upload-nextcloud endpoint
Endpoint /api/jobs/{id}/upload-nextcloud je že obstajal (commit dbb8ab3)
in deluje. Moja nova varianta je bila duplikat — odstranjena.

Aktivni endpoint: line 1593 (upload_nextcloud), uporablja
_nextcloud_upload() in _nextcloud_configured() helper-je.

Zdaj imamo:
- _safe_filename_for_nextcloud() helper (ostane, lahko pride prav)
- Frontend gumb '☁ Nextcloud' z 4 stanji (default/uploading/uploaded/failed)
- 14 reelov že uspešno uploadanih v /folxspeed/REELS/FOLX SLOVENIJA/
2026-04-30 14:32:04 +00:00
d03beddd0d Nextcloud: URL-encode path segments + use FOLX SLOVENIJA subfolder
- NEXTCLOUD_FOLDER env updated: folxspeed/REELS → folxspeed/REELS/FOLX SLOVENIJA
- urllib.parse.quote() each segment (handles spaces in folder names)
- e.g. 'FOLX SLOVENIJA' → 'FOLX%20SLOVENIJA' in URL
2026-04-30 14:27:28 +00:00
dbb8ab3059 Nextcloud upload za FOLX SLOVENIJA reels
User wants reels saved directly to Nextcloud /folxspeed/REELS/FOLX SLOVENIJA/

NEW backend endpoint: POST /api/jobs/{id}/upload-nextcloud
- WebDAV PUT preko stdlib urllib (no new deps)
- Uses NEXTCLOUD_URL/USER/PASS/REELS_PATH env vars
- Updates job status: uploading → uploaded / error
- Stores nextcloud_url + nextcloud_error in job

Frontend already had button (☁ Nextcloud) and handler — just needed
backend endpoint. UI states:
- ☁ Nextcloud (blue) — not yet uploaded
- ☁ ✓ Nextcloud (green) — uploaded successfully
- ☁ ✕ Poskusi znova (red) — upload failed (hover for error)

Env vars added in Coolify:
- NEXTCLOUD_URL=https://nextcloud.folx.tv
- NEXTCLOUD_USER=admin
- NEXTCLOUD_PASS=<app token>
- NEXTCLOUD_REELS_PATH=folxspeed/REELS/FOLX SLOVENIJA
2026-04-30 14:23:15 +00:00
faf002d4f8 UI: posodobi zastarel opis (Whisper → Soniox + Claude)
User feedback: 'tukaj imava cel kup stvari ki niso res, kako oblikujemo?'

Old text was misleading:
- 'Whisper, 3-sample voting' → not used since Soniox integration
- 'Model: medium' → irrelevant (Whisper not used)
- 'Whisper + energy → najde refren' → now Soniox + Claude LLM

New text reflects actual stack:
- STT: Soniox (primary) → ElevenLabs Scribe → Gemini fallback
- LLM: Claude Sonnet 4.6
- Energy profile + word-level timestamps + 15 reference examples
- Mention ✏️ Edit button for manual fine-tuning
2026-04-30 14:13:48 +00:00
de094b76a5 Edit: add 'Konec (5s)' button — preview just last 5s of clip
User feedback: 'ko hočemo preveriti konec, predvajaj samo 5s.
začetek ni problematičen ker plej začne od začetka'

NEW button: ▶ Konec (5s) — green
- Seeks to (trimEnd - 5s)
- Plays from there to trimEnd
- Auto-stops at trimEnd (existing logic)
- Quick way to verify if 'OUT' position is correct without
  waiting for full clip playback (which can be 30-60s)

Renamed: '▶ Predvajaj odsek' → '▶ Predvajaj cel' for clarity
(plays full clip from start to end)

Workflow now:
- Adjust handles
- '▶ Predvajaj cel' to hear whole clip (when needed)
- '▶ Konec (5s)' to quickly check if end is right
- Iterate handles until perfect
- Save
2026-04-30 14:04:36 +00:00
02fbae7c4f Edit: triangles INSIDE trim bar (overflow:hidden was clipping them)
Bug: triangles positioned at top:-14px were outside trim bar bounds.
Trim bar has overflow:hidden, so triangles were clipped (invisible).

Fix: top:0 (inside trim bar, at the very top edge).
Triangle 14px tall now sits at top of trim bar (overlapping waveform
slightly but visible, with drop-shadow to make them stand out).
2026-04-30 13:57:09 +00:00
5d817c586b Edit: IN/OUT marker triangles + Postavi IN/OUT buttons
User feedback: Workflow is - click + Enter sets a marker triangle, then
button moves the red handle to that triangle. Triangle near LEFT handle
= IN candidate (green), near RIGHT = OUT candidate (red).

Visual:
- Green triangle (▼) above trim bar = IN candidate position
- Red triangle (▼) above trim bar = OUT candidate position
- White line (playhead) = current video position (moves during playback)
- Red handles (existing) = actual clip start/end

Workflow:
1. Click on waveform → white playhead jumps there
2. Press Enter → playhead starts moving (plays)
   ALSO: triangle gets placed at current position
   - If position closer to LEFT handle → green IN triangle
   - If position closer to RIGHT handle → red OUT triangle
3. Listen, decide 'this is the right spot'
4. Click ▼ Postavi IN button → red LEFT handle jumps to green triangle
   (or ▼ Postavi OUT for right handle)
5. Now red handle and triangle are aligned = clip boundary committed

Triangles persist until next play press (= next candidate).
Buttons styled with matching color (green for IN, red for OUT).
2026-04-30 13:42:30 +00:00