하이라이트 조립 — build_highlight_cuts() 모드 분기 구현
통짜 모드(컷 1개 + 자막 없음)는 시각 슬롯으로 배정, 컷 있는 모드는 Gemini 추천. is_whole() 판별, _whole_picks() 슬롯 채우기, build_highlight_cuts() 조립. import에 match_slots, top_liked 추가. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
88768020bf
commit
005beba4c3
@ -14,7 +14,7 @@ import urllib.request
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from . import prompts
|
from . import prompts
|
||||||
from .comments import MAX_TIMES, match_ranges
|
from .comments import MAX_TIMES, match_ranges, match_slots, top_liked
|
||||||
from .correct import _gemini_key
|
from .correct import _gemini_key
|
||||||
|
|
||||||
# 카드 1장이 차지하는 기준 시간(초). pipeline._load_comment_cards(min_sec) 과 같은 값.
|
# 카드 1장이 차지하는 기준 시간(초). pipeline._load_comment_cards(min_sec) 과 같은 값.
|
||||||
@ -153,3 +153,51 @@ def ai_pick_cuts(cuts, comments, quotas, *, model: str = AI_MODEL,
|
|||||||
OSError): # URLError, TimeoutError are OSError subclasses; ValueError covers UnicodeDecodeError
|
OSError): # URLError, TimeoutError are OSError subclasses; ValueError covers UnicodeDecodeError
|
||||||
return {} # 429 포함 — 폴백은 호출부 몫
|
return {} # 429 포함 — 폴백은 호출부 몫
|
||||||
return _parse_ai(raw)
|
return _parse_ai(raw)
|
||||||
|
|
||||||
|
|
||||||
|
def is_whole(cuts) -> bool:
|
||||||
|
"""통짜 하이라이트인가 — 컷 1개 + 자막 없음(app._whole_hl 이 만드는 모양).
|
||||||
|
|
||||||
|
통짜는 자막이 없어 내용 추천의 근거가 없다. 대신 타임라인 시각 = 원본 시각이라
|
||||||
|
시각으로 정확히 맞출 수 있다(스펙 §4.1).
|
||||||
|
"""
|
||||||
|
return len(cuts) == 1 and not (cuts[0].get("bottom") or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _whole_picks(cut, comments, n: int) -> List[dict]:
|
||||||
|
"""통짜 — 슬롯마다 그 시간대 언급 댓글, 빈 슬롯은 좋아요 상위로 메움."""
|
||||||
|
slots = match_slots(comments, cut["start"], cut["end"] - cut["start"], n)
|
||||||
|
used = {i for i in slots if i is not None}
|
||||||
|
no_ts = [c for c in comments if not c.get("times")]
|
||||||
|
fill = iter(top_liked(no_ts, used, n))
|
||||||
|
out: List[dict] = []
|
||||||
|
for s in slots:
|
||||||
|
if s is not None:
|
||||||
|
out.append({"idx": s, "why": "ts"})
|
||||||
|
continue
|
||||||
|
nxt = next(fill, None)
|
||||||
|
if nxt is not None:
|
||||||
|
out.append({"idx": nxt, "why": "like"})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def build_highlight_cuts(hl, comments, *, key=None):
|
||||||
|
"""하이라이트 하나 → (cuts[], need). 모드는 컷 모양으로 판별한다.
|
||||||
|
|
||||||
|
Returns: ([{"i","sec","bottom","quota","picks"}], need)
|
||||||
|
need = Σ quota — 기존 int(total//3) 을 대체한다(컷 경계에 맞추는 쪽이 맞다).
|
||||||
|
Gemini 실패는 여기서 흡수된다(ai_pick_cuts 가 {} 를 준다) — 예외를 올리지 않는다.
|
||||||
|
"""
|
||||||
|
cuts = (hl.get("paste") or {}).get("cuts") or []
|
||||||
|
if not cuts:
|
||||||
|
return [], 0
|
||||||
|
quotas = quotas_for(cuts)
|
||||||
|
if is_whole(cuts):
|
||||||
|
picks = [_whole_picks(cuts[0], comments, quotas[0])]
|
||||||
|
else:
|
||||||
|
ai = ai_pick_cuts(cuts, comments, quotas, key=key)
|
||||||
|
picks = build_cut_picks(cuts, comments, ai, quotas)
|
||||||
|
out = [{"i": i, "sec": round(c["end"] - c["start"], 1),
|
||||||
|
"bottom": c.get("bottom") or "", "quota": quotas[i], "picks": picks[i]}
|
||||||
|
for i, c in enumerate(cuts)]
|
||||||
|
return out, sum(quotas)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user