diff --git a/capcut_agent/recommend.py b/capcut_agent/recommend.py index c9911a2..002c70e 100644 --- a/capcut_agent/recommend.py +++ b/capcut_agent/recommend.py @@ -97,7 +97,12 @@ def build_cut_picks(cuts, comments, ai_picks: Optional[Dict[int, List[int]]], 단어 겹침이 AI 다음인 이유: Gemini 는 문맥까지 보고 고르니 더 정확하지만, 503 등으로 실패하거나(`ai_picks=None`) 문맥상 연결을 놓칠 때(예: "와인 뱉는 장면" ↔ "싱크대로 달려간 이유"는 겹치는 단어가 없다) 그물을 하나 더 치는 것 — 네트워크 없이도 항상 동작한다. - 중복은 앞 컷이 가져간다(뒤 컷은 다음 후보로 밀린다). + + ⚠ 컷별로 4단계를 다 채우고 다음 컷으로 넘어가면 안 된다 — 앞 컷의 약한 근거(4순위 + 좋아요)가 뒤 컷의 강한 근거(3순위 단어 겹침)보다 먼저 댓글을 가져가 버린다. 그래서 + **단계(라운드)를 바깥 루프, 컷을 안쪽 루프**로 둔다 — 전 컷의 ts를 다 채운 뒤에야 + 전 컷의 ai로, 그다음에야 word로 넘어간다. 순위가 컷 순서보다 우선한다. + 중복은 앞 컷이 가져간다(뒤 컷은 다음 후보로 밀린다) — `used` 는 라운드·컷을 통틀어 공유. ai_picks: {컷인덱스: [댓글idx …]} — Gemini 실패 시 None/{} 어느 쪽을 넘겨도 타임스탬프만으로 채운다(`ai_pick_cuts` 는 실패를 None 으로 알린다). @@ -107,33 +112,43 @@ def build_cut_picks(cuts, comments, ai_picks: Optional[Dict[int, List[int]]], valid = {c["idx"] for c in comments} no_ts = [c for c in comments if not c.get("times")] used: set = set() - out: List[List[dict]] = [] + quota = [quotas[i] if i < len(quotas) else 0 for i in range(len(cuts))] + out: List[List[dict]] = [[] for _ in cuts] + + # 1라운드: ⭐시각 for i, cut in enumerate(cuts): - quota = quotas[i] if i < len(quotas) else 0 - picks: List[dict] = [] for idx in match_ranges(comments, [(cut["start"], cut["end"])]): - if len(picks) >= quota: + if len(out[i]) >= quota[i]: break if idx not in used: - picks.append({"idx": idx, "why": "ts"}) + out[i].append({"idx": idx, "why": "ts"}) used.add(idx) + # 2라운드: 🤖AI + for i in range(len(cuts)): + if len(out[i]) >= quota[i]: + continue for idx in (ai_picks.get(i) or []): - if len(picks) >= quota: + if len(out[i]) >= quota[i]: break if idx in valid and idx not in used: - picks.append({"idx": idx, "why": "ai"}) + out[i].append({"idx": idx, "why": "ai"}) used.add(idx) - if len(picks) < quota: - for idx in _word_match_ranked(cut, comments, used): - if len(picks) >= quota: - break - picks.append({"idx": idx, "why": "word"}) - used.add(idx) - if len(picks) < quota: - for idx in top_liked(no_ts, used, quota - len(picks)): - picks.append({"idx": idx, "why": "like"}) - used.add(idx) - out.append(picks) + # 3라운드: 🔤단어겹침 + for i, cut in enumerate(cuts): + if len(out[i]) >= quota[i]: + continue + for idx in _word_match_ranked(cut, comments, used): + if len(out[i]) >= quota[i]: + break + out[i].append({"idx": idx, "why": "word"}) + used.add(idx) + # 4라운드: ➕좋아요 + for i in range(len(cuts)): + if len(out[i]) >= quota[i]: + continue + for idx in top_liked(no_ts, used, quota[i] - len(out[i])): + out[i].append({"idx": idx, "why": "like"}) + used.add(idx) return out