Major: smart selection pipeline (analyze.py) + audio fade + multi-lang auto-detect
- New analyze.py: full transcript + energy + structural analysis - Smart clip range: includes pre-chorus, can exceed 30s up to max_duration (default 45s) - Audio fade in/out: auto-detected from vocal boundaries - Instrumental detection: auto-disables subs if vocals < 10% of duration - Multi-language: auto-detect via Whisper or explicit (DE/SL/HR/BS/SR/EN/IT/ES/FR) - Frontend: cleaner UX, added bs language, smart selection description - reframe.py: --fade-in --fade-out args - clip.py: propagates fade params - app/main.py: replaces find_chorus.py call with analyze.py
This commit is contained in:
+56
-24
@@ -154,37 +154,57 @@ def process_job(job_id):
|
||||
else:
|
||||
input_path = Path(job["input_path"])
|
||||
|
||||
# ── 2. Find chorus (če auto) ──────────────────────────
|
||||
# ── 2. Smart analysis (če auto_chorus) ──────────────────────────
|
||||
if job.get("auto_chorus"):
|
||||
update_job(job_id, current_step="Iščem refren (Whisper + energy)")
|
||||
update_job(job_id, current_step="Analiza pesmi (transkript + energija)")
|
||||
analysis_path = OUTPUT_DIR / f"{job_id}.analysis.json"
|
||||
cmd = [
|
||||
"python3", str(SCRIPTS_DIR / "find_chorus.py"),
|
||||
"python3", str(SCRIPTS_DIR / "analyze.py"),
|
||||
str(input_path),
|
||||
"--duration", str(job.get("duration", 30)),
|
||||
"--json",
|
||||
"--target-duration", str(job.get("duration", 30)),
|
||||
"--max-duration", str(job.get("max_duration", 45)),
|
||||
"--min-duration", str(job.get("min_duration", 20)),
|
||||
"--output", str(analysis_path),
|
||||
]
|
||||
if job.get("lang"):
|
||||
# lang: če None ali 'auto', pusti analyze.py auto-detect
|
||||
if job.get("lang") and job["lang"] not in ("auto", ""):
|
||||
cmd += ["--lang", job["lang"]]
|
||||
cmd += ["--model", job.get("whisper_model", "small")]
|
||||
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if proc.returncode == 0:
|
||||
if proc.returncode == 0 and analysis_path.exists():
|
||||
try:
|
||||
chorus = json.loads(proc.stdout)
|
||||
if chorus.get("candidates"):
|
||||
best = chorus["candidates"][0]
|
||||
update_job(
|
||||
job_id,
|
||||
chorus_detection=chorus,
|
||||
start=best["start"],
|
||||
duration=best["duration"],
|
||||
)
|
||||
# KLJUČNO: reload local job dict, da nove vrednosti pridejo v reframe call
|
||||
job = load_job(job_id)
|
||||
except json.JSONDecodeError:
|
||||
update_job(job_id, chorus_error="JSON decode failed")
|
||||
with open(analysis_path, "r", encoding="utf-8") as f:
|
||||
analysis = json.load(f)
|
||||
cr = analysis["clip_range"]
|
||||
fade = analysis["fade"]
|
||||
update_job(
|
||||
job_id,
|
||||
analysis_summary={
|
||||
"language": analysis["language"],
|
||||
"language_probability": analysis["language_probability"],
|
||||
"instrumental": analysis["instrumental"],
|
||||
"clip_range": cr,
|
||||
"fade": fade,
|
||||
"chorus_preview": analysis["chorus"]["best"]["text_preview"]
|
||||
if analysis.get("chorus") and analysis["chorus"].get("best") else None,
|
||||
},
|
||||
start=cr["start"],
|
||||
duration=cr["duration"],
|
||||
fade_in=fade["fade_in"],
|
||||
fade_out=fade["fade_out"],
|
||||
detected_language=analysis["language"],
|
||||
is_instrumental=analysis["instrumental"],
|
||||
)
|
||||
# Auto-disable subs za instrumental
|
||||
if analysis["instrumental"] and not job.get("no_subs"):
|
||||
update_job(job_id, no_subs=True, auto_disabled_subs=True)
|
||||
# Reload local dict
|
||||
job = load_job(job_id)
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
update_job(job_id, chorus_error=f"Analysis parse: {e}")
|
||||
else:
|
||||
update_job(job_id, chorus_error=proc.stderr[-300:])
|
||||
update_job(job_id, chorus_error=(proc.stderr or "")[-500:])
|
||||
|
||||
# ── 3. Reframe + subtitles (clip.py orchestrator) ─────
|
||||
output_path = OUTPUT_DIR / f"{job_id}.mp4"
|
||||
@@ -201,8 +221,16 @@ def process_job(job_id):
|
||||
cmd += ["--start", str(job["start"])]
|
||||
if job.get("duration") is not None:
|
||||
cmd += ["--duration", str(job["duration"])]
|
||||
if job.get("lang"):
|
||||
cmd += ["--lang", job["lang"]]
|
||||
if job.get("fade_in", 0) > 0:
|
||||
cmd += ["--fade-in", str(job["fade_in"])]
|
||||
if job.get("fade_out", 0) > 0:
|
||||
cmd += ["--fade-out", str(job["fade_out"])]
|
||||
# lang: prefer detected_language če auto
|
||||
chosen_lang = job.get("lang")
|
||||
if chosen_lang in (None, "auto", ""):
|
||||
chosen_lang = job.get("detected_language")
|
||||
if chosen_lang:
|
||||
cmd += ["--lang", chosen_lang]
|
||||
if job.get("no_subs"):
|
||||
cmd += ["--no-subs"]
|
||||
cmd += ["--model", job.get("whisper_model", "small")]
|
||||
@@ -269,10 +297,12 @@ class YouTubeJobIn(BaseModel):
|
||||
class StartJobIn(BaseModel):
|
||||
job_id: str
|
||||
mode: str = "track"
|
||||
lang: Optional[str] = None
|
||||
lang: Optional[str] = None # None/auto = Whisper auto-detect
|
||||
auto_chorus: bool = True
|
||||
start: Optional[float] = None
|
||||
duration: Optional[float] = 30
|
||||
max_duration: Optional[float] = 45 # Smart selection lahko gre do 45s
|
||||
min_duration: Optional[float] = 20
|
||||
no_subs: bool = False
|
||||
subtitle_style: str = "reels"
|
||||
whisper_model: str = "small"
|
||||
@@ -373,6 +403,8 @@ async def start_processing(
|
||||
auto_chorus=payload.auto_chorus,
|
||||
start=payload.start,
|
||||
duration=payload.duration,
|
||||
max_duration=payload.max_duration,
|
||||
min_duration=payload.min_duration,
|
||||
no_subs=payload.no_subs,
|
||||
subtitle_style=payload.subtitle_style,
|
||||
whisper_model=payload.whisper_model,
|
||||
|
||||
Reference in New Issue
Block a user