feat: 구간 JSON에 title_top/title_main 선택 필드 — 검토 화면 제목 자동 채움
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b49946a4cd
commit
f38776d71f
@ -93,10 +93,12 @@ def _extract_json_str(text: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def parse_candidates(text: str, *, src: str = "Step 1 응답") -> List[Dict]:
|
def parse_candidates(text: str, *, src: str = "Step 1 응답") -> List[Dict]:
|
||||||
"""`{"candidates":[{id,start_time,end_time,reason}, …]}` → [{id,start,end,reason}] (초).
|
"""`{"candidates":[{id,start_time,end_time,reason}, …]}` → [{id,start,end,reason,title_top,title_main}] (초).
|
||||||
|
|
||||||
Gemini Step1 응답과 사용자가 직접 붙여넣는 구간 JSON이 **같은 형식**이라 둘이 공유한다.
|
Gemini Step1 응답과 사용자가 직접 붙여넣는 구간 JSON이 **같은 형식**이라 둘이 공유한다.
|
||||||
코드펜스(```)·앞뒤 잡텍스트는 `_extract_json_str`이 걷어낸다.
|
코드펜스(```)·앞뒤 잡텍스트는 `_extract_json_str`이 걷어낸다.
|
||||||
|
`title_top`/`title_main`은 선택 — 구간 JSON에 있으면 검토 화면 제목 입력칸에
|
||||||
|
미리 채워진다(Step1 응답에는 없으므로 빈 문자열).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
data = json.loads(_extract_json_str(text), strict=False)
|
data = json.loads(_extract_json_str(text), strict=False)
|
||||||
@ -116,7 +118,9 @@ def parse_candidates(text: str, *, src: str = "Step 1 응답") -> List[Dict]:
|
|||||||
continue
|
continue
|
||||||
if e > s:
|
if e > s:
|
||||||
out.append({"id": int(c.get("id") or i), "start": s, "end": e,
|
out.append({"id": int(c.get("id") or i), "start": s, "end": e,
|
||||||
"reason": str(c.get("reason") or "").strip()})
|
"reason": str(c.get("reason") or "").strip(),
|
||||||
|
"title_top": str(c.get("title_top") or "").strip(),
|
||||||
|
"title_main": str(c.get("title_main") or "").strip()})
|
||||||
if not out:
|
if not out:
|
||||||
raise RuntimeError(f"{src}: 유효한 구간이 하나도 없습니다.")
|
raise RuntimeError(f"{src}: 유효한 구간이 하나도 없습니다.")
|
||||||
return out
|
return out
|
||||||
|
|||||||
@ -526,13 +526,16 @@ async def auto_stream(aid: str) -> StreamingResponse:
|
|||||||
|
|
||||||
def _whole_hl(c: dict) -> dict:
|
def _whole_hl(c: dict) -> dict:
|
||||||
"""구간 통짜 하이라이트 — 컷 편집 없이 구간 전체가 컷 1개.
|
"""구간 통짜 하이라이트 — 컷 편집 없이 구간 전체가 컷 1개.
|
||||||
제목은 비워 두고 `editable_title` 로 UI에서 직접 입력받는다.
|
제목은 `editable_title` 로 UI에서 직접 입력받되, 구간 JSON에
|
||||||
|
title_top/title_main 이 있으면 그 값을 입력칸에 미리 채운다.
|
||||||
whole(Gemini Step1) 과 wpaste(구간 JSON 붙여넣기) 가 공유."""
|
whole(Gemini Step1) 과 wpaste(구간 JSON 붙여넣기) 가 공유."""
|
||||||
total = c["end"] - c["start"]
|
total = c["end"] - c["start"]
|
||||||
return {
|
return {
|
||||||
"id": c["id"], "start": c["start"], "end": c["end"],
|
"id": c["id"], "start": c["start"], "end": c["end"],
|
||||||
"reason": c["reason"],
|
"reason": c["reason"],
|
||||||
"paste": {"url": url, "title_top": "", "title_main": "",
|
"paste": {"url": url,
|
||||||
|
"title_top": c.get("title_top") or "",
|
||||||
|
"title_main": c.get("title_main") or "",
|
||||||
"channel": "",
|
"channel": "",
|
||||||
"cuts": [{"start": c["start"], "end": c["end"],
|
"cuts": [{"start": c["start"], "end": c["end"],
|
||||||
"bottom": "", "effect": ""}]},
|
"bottom": "", "effect": ""}]},
|
||||||
|
|||||||
@ -532,15 +532,15 @@ function curMode(){
|
|||||||
const MODE_HELP={
|
const MODE_HELP={
|
||||||
full:"Gemini가 영상에서 하이라이트 구간 5개를 골라 편집안을 만들고, h-lab에서 그 구간을 언급한 댓글을 찾아옵니다. 무음 제거·받아쓰기(Whisper)는 댓글 매칭을 위해 항상 적용됩니다(끌 수 없음).",
|
full:"Gemini가 영상에서 하이라이트 구간 5개를 골라 편집안을 만들고, h-lab에서 그 구간을 언급한 댓글을 찾아옵니다. 무음 제거·받아쓰기(Whisper)는 댓글 매칭을 위해 항상 적용됩니다(끌 수 없음).",
|
||||||
whole:"Gemini가 구간 5개만 고르고(Step1), 각 구간을 컷 편집 없이 통짜로 만듭니다. 무음 제거·받아쓰기(Whisper)는 댓글 매칭을 위해 항상 적용됩니다(끌 수 없음).",
|
whole:"Gemini가 구간 5개만 고르고(Step1), 각 구간을 컷 편집 없이 통짜로 만듭니다. 무음 제거·받아쓰기(Whisper)는 댓글 매칭을 위해 항상 적용됩니다(끌 수 없음).",
|
||||||
wpaste:"Gemini를 쓰지 않습니다. 구간 JSON(candidates 5개)을 붙여넣으면 그 구간을 그대로 통짜로 만듭니다. 제목(윗줄·아랫줄)은 댓글 선택 화면에서 ID별로 직접 씁니다. 무음 제거·받아쓰기(Whisper)는 댓글 매칭을 위해 항상 적용됩니다(끌 수 없음).",
|
wpaste:"Gemini를 쓰지 않습니다. 구간 JSON(candidates 5개)을 붙여넣으면 그 구간을 그대로 통짜로 만듭니다. JSON에 title_top·title_main이 있으면 제목(윗줄·아랫줄)이 자동으로 채워지고, 검토 화면에서 ID별로 고칠 수 있습니다. 무음 제거·받아쓰기(Whisper)는 댓글 매칭을 위해 항상 적용됩니다(끌 수 없음).",
|
||||||
paste:"Gemini를 쓰지 않습니다. 오팔에서 받은 JSON 5개를 통째로 붙여넣으면 댓글 선택 화면으로 갑니다. URL을 비우면 JSON 안의 url을 씁니다. 무음 제거·받아쓰기(Whisper)는 댓글 매칭을 위해 항상 적용됩니다(끌 수 없음).",
|
paste:"Gemini를 쓰지 않습니다. 오팔에서 받은 JSON 5개를 통째로 붙여넣으면 댓글 선택 화면으로 갑니다. URL을 비우면 JSON 안의 url을 씁니다. 무음 제거·받아쓰기(Whisper)는 댓글 매칭을 위해 항상 적용됩니다(끌 수 없음).",
|
||||||
};
|
};
|
||||||
const PASTE_UI={
|
const PASTE_UI={
|
||||||
paste:{label:"오팔 JSON 붙여넣기 (여러 개를 통째로 — 사이에 구분선·타이틀 후보가 섞여 있어도 됨)",
|
paste:{label:"오팔 JSON 붙여넣기 (여러 개를 통째로 — 사이에 구분선·타이틀 후보가 섞여 있어도 됨)",
|
||||||
ph:'오팔 Step 3 결과(JSON 코드블록) 5개를 순서대로 전부 붙여넣으세요.\n블록 ②·③ 텍스트가 섞여 들어와도 자동으로 JSON만 골라냅니다.',
|
ph:'오팔 Step 3 결과(JSON 코드블록) 5개를 순서대로 전부 붙여넣으세요.\n블록 ②·③ 텍스트가 섞여 들어와도 자동으로 JSON만 골라냅니다.',
|
||||||
note:"(선택 — 비우면 JSON의 url 사용)"},
|
note:"(선택 — 비우면 JSON의 url 사용)"},
|
||||||
wpaste:{label:"구간 JSON 붙여넣기 (candidates 배열 — 구간 5개)",
|
wpaste:{label:"구간 JSON 붙여넣기 (candidates 배열 — 구간 5개 · title_top/title_main 선택)",
|
||||||
ph:'{\n "candidates": [\n {"id": 1, "start_time": "14:28", "end_time": "16:15", "reason": "구간 선정 이유"},\n … (총 5개)\n ]\n}',
|
ph:'{\n "candidates": [\n {"id": 1, "start_time": "14:28", "end_time": "16:15", "reason": "구간 선정 이유",\n "title_top": "상단 제목 (선택)", "title_main": "메인 제목 (선택)"},\n … (총 5개)\n ]\n}',
|
||||||
note:"(필수 — 구간 JSON에는 URL이 없습니다)"},
|
note:"(필수 — 구간 JSON에는 URL이 없습니다)"},
|
||||||
};
|
};
|
||||||
function applyMode(){
|
function applyMode(){
|
||||||
@ -654,6 +654,11 @@ function onResult(ev){
|
|||||||
(t.kind?" — "+esc(t.kind):"")+"</option>").join("")+"</select></div>";
|
(t.kind?" — "+esc(t.kind):"")+"</option>").join("")+"</select></div>";
|
||||||
}
|
}
|
||||||
card.innerHTML=html;
|
card.innerHTML=html;
|
||||||
|
if(hl.editable_title){ // 구간 JSON의 title_top/title_main 미리 채움 (.value라 따옴표 이스케이프 불필요)
|
||||||
|
const ti=card.querySelector("#hltop-"+hl.id),mi=card.querySelector("#hlmain-"+hl.id);
|
||||||
|
if(ti) ti.value=hl.paste.title_top||"";
|
||||||
|
if(mi) mi.value=hl.paste.title_main||"";
|
||||||
|
}
|
||||||
card.dataset.titles=JSON.stringify(opts);
|
card.dataset.titles=JSON.stringify(opts);
|
||||||
card.appendChild(cutsSection(hl)); // 영상 편집안(컷 목록·JSON) — 기본 접힘
|
card.appendChild(cutsSection(hl)); // 영상 편집안(컷 목록·JSON) — 기본 접힘
|
||||||
R.appendChild(card);
|
R.appendChild(card);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user