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'.
This commit is contained in:
Claude 2026-05-03 14:26:42 +00:00
parent cd872d8bea
commit 576cc807b5
2 changed files with 24 additions and 14 deletions

View File

@ -333,8 +333,18 @@ def parse_artist_title(filename_or_title):
if not filename_or_title:
return (None, None)
# Odstrani extension
name = Path(filename_or_title).stem if "." in filename_or_title else filename_or_title
# Odstrani extension SAMO če je dejansko file extension (kratko, brez presledka).
# Path("ANS.NAVEZA - SREČA OPOTEČA").stem vrne "ANS" — narobe za YT title.
# Pravilno: samo če je string brez presledkov ali se konča na običajno ext.
_COMMON_EXT = {".mp4", ".mp3", ".m4a", ".webm", ".mkv", ".avi", ".mov",
".wav", ".flac", ".aac", ".opus", ".ogg", ".wmv", ".mxf"}
name = filename_or_title
if "." in name:
# Vzemi zadnjo piko in preveri ali je to znana ext
last_dot = name.rfind(".")
ext = name[last_dot:].lower()
if ext in _COMMON_EXT and last_dot > 0:
name = name[:last_dot]
# Odstrani noise patterns
for pat in _NOISE_PATTERNS:
@ -1032,7 +1042,7 @@ def process_job(job_id):
if final_job.get("auto_upload_to_nextcloud") and _nextcloud_configured():
update_job(job_id, nextcloud_status="uploading", nextcloud_error=None)
download_name = build_download_filename(final_job)
tv_station = final_job.get("tv_station", "FOLX SLOVENIJA")
tv_station = final_job.get("tv_station", "FOLX SLO")
nc_folder = _nextcloud_folder_for_station(tv_station)
target_subdir = f"folxspeed/REELS/{nc_folder}"
print(f"☁️ Avto-upload v Nextcloud: /{target_subdir}/{download_name}", flush=True)
@ -1300,7 +1310,7 @@ class YouTubeJobIn(BaseModel):
subtitle_style: str = "reels"
whisper_model: str = "large-v3"
quality: str = "medium"
tv_station: str = "FOLX SLOVENIJA" # za Nextcloud target mapo
tv_station: str = "FOLX SLO" # za Nextcloud target mapo
class StartJobIn(BaseModel):
@ -1325,7 +1335,7 @@ class StartJobIn(BaseModel):
# Batch tracking za multi-upload (Telegram summary)
batch_id: Optional[str] = None
# TV postaja — določa Nextcloud target mapo
tv_station: str = "FOLX SLOVENIJA"
tv_station: str = "FOLX SLO"
# ────────────────────────────────────────────────────────────────
@ -1333,7 +1343,7 @@ class StartJobIn(BaseModel):
# ────────────────────────────────────────────────────────────────
class DedupCheckRequest(BaseModel):
filenames: list[str]
tv_station: str = "FOLX SLOVENIJA"
tv_station: str = "FOLX SLO"
@app.post("/api/dedup/check")
@ -2308,7 +2318,7 @@ async def upload_nextcloud(job_id: str, user: str = Depends(check_auth)):
download_name = build_download_filename(job)
# TV postaja določa target mapo (mapping Qnet -> Nextcloud folder)
tv_station = job.get("tv_station", "FOLX SLOVENIJA")
tv_station = job.get("tv_station", "FOLX SLO")
nc_folder = _nextcloud_folder_for_station(tv_station)
target_subdir = f"folxspeed/REELS/{nc_folder}"

View File

@ -351,13 +351,13 @@
<!-- TV postaja: določa Nextcloud target mapo -->
<label style="margin-top:8px;">📺 TV postaja (določa kam gre na Nextcloud)</label>
<div class="tv-station-tabs" style="display:flex; flex-wrap:wrap; gap:6px; margin-bottom:14px;">
<button type="button" class="tv-tab active" data-station="FOLX SLOVENIJA" style="padding:6px 12px; border:1px solid var(--accent); background:var(--accent); color:#fff; border-radius:4px; cursor:pointer; font-size:13px;">FOLX SLOVENIJA</button>
<button type="button" class="tv-tab active" data-station="FOLX SLO" style="padding:6px 12px; border:1px solid var(--accent); background:var(--accent); color:#fff; border-radius:4px; cursor:pointer; font-size:13px;">FOLX SLO</button>
<button type="button" class="tv-tab" data-station="FOLX DE" style="padding:6px 12px; border:1px solid #444; background:transparent; color:#ccc; border-radius:4px; cursor:pointer; font-size:13px;">FOLX DE</button>
<button type="button" class="tv-tab" data-station="ONE DE" style="padding:6px 12px; border:1px solid #444; background:transparent; color:#ccc; border-radius:4px; cursor:pointer; font-size:13px;">ONE DE</button>
<button type="button" class="tv-tab" data-station="ZWEI MUSIC" style="padding:6px 12px; border:1px solid #444; background:transparent; color:#ccc; border-radius:4px; cursor:pointer; font-size:13px;">ZWEI MUSIC</button>
<button type="button" class="tv-tab" data-station="ADRIA" style="padding:6px 12px; border:1px solid #444; background:transparent; color:#ccc; border-radius:4px; cursor:pointer; font-size:13px;">ADRIA</button>
</div>
<input type="hidden" id="tv-station-input" value="FOLX SLOVENIJA">
<input type="hidden" id="tv-station-input" value="FOLX SLO">
<!-- Skriti settings — vsi defaulti zapečeni za reels workflow -->
<input type="hidden" id="mode" value="track">
@ -428,8 +428,8 @@
<button type="button" class="station-filter-tab active" data-station="" style="padding:5px 11px; border:1px solid var(--accent); background:var(--accent); color:#fff; border-radius:4px; cursor:pointer; font-size:12px;">
Vse <span class="cnt" data-cnt-all>0</span>
</button>
<button type="button" class="station-filter-tab" data-station="FOLX SLOVENIJA" style="padding:5px 11px; border:1px solid #444; background:transparent; color:#ccc; border-radius:4px; cursor:pointer; font-size:12px;">
FOLX SLOVENIJA <span class="cnt" data-cnt="FOLX SLOVENIJA">0</span>
<button type="button" class="station-filter-tab" data-station="FOLX SLO" style="padding:5px 11px; border:1px solid #444; background:transparent; color:#ccc; border-radius:4px; cursor:pointer; font-size:12px;">
FOLX SLO <span class="cnt" data-cnt="FOLX SLO">0</span>
</button>
<button type="button" class="station-filter-tab" data-station="FOLX DE" style="padding:5px 11px; border:1px solid #444; background:transparent; color:#ccc; border-radius:4px; cursor:pointer; font-size:12px;">
FOLX DE <span class="cnt" data-cnt="FOLX DE">0</span>
@ -536,7 +536,7 @@
}
const filenames = newItems.map(i => i.file.name);
const tvStation = $("#tv-station-input").value || "FOLX SLOVENIJA";
const tvStation = $("#tv-station-input").value || "FOLX SLO";
// Vzporedno: dedup check + Qnet match (oba endpointa, neodvisna)
const [dedupRes, qnetRes] = await Promise.all([
@ -681,7 +681,7 @@
quality: $("#quality").value,
no_subs: $("#no-subs").checked,
llm_provider: $("#llm-provider").value,
tv_station: $("#tv-station-input").value || "FOLX SLOVENIJA",
tv_station: $("#tv-station-input").value || "FOLX SLO",
};
}
@ -1271,7 +1271,7 @@
actions.push(`<button class="small ghost" data-action="delete" data-id="${job.id}"></button>`);
// TV station label (brez emoji)
const tvStation = job.tv_station || "FOLX SLOVENIJA";
const tvStation = job.tv_station || "FOLX SLO";
const tvBadge = `<span style="font-size:10px; padding:2px 6px; background:rgba(255,107,107,0.15); border:1px solid rgba(255,107,107,0.4); border-radius:3px; color:#ff8c8c; font-weight:600; white-space:nowrap;">${escapeHtml(tvStation)}</span>`;
el.innerHTML = `