댓글 카드를 컷 구간 안에 배치하는 경로 추가
_load_comment_cards를 _card_paths/_load_comment_cards/_cards_by_cut로 분리했다. 자동 탭은 카드가 어느 컷 소속인지(card_cuts)만 넘기면 그 컷 구간 안에서 균등 배치되어, 한 컷의 카드가 모자라도 다음 컷 카드가 앞으로 밀리지 않는다(기존 전체 균등 배치의 약점). 카드 시간은 무음 제거 전 placements 직후에 계산하고, 자막과 동일하게 _remap_caps()로 재매핑한다 — 카드 튜플(start,end,path)이 자막 튜플(start,end,text)과 모양이 같아 그대로 재사용 가능하다. card_cuts가 없으면 기존 _load_comment_cards 폴백으로 떨어져 파일/유튜브 구간 탭은 영향 없다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
90ff2b174f
commit
0a0b43efc2
@ -35,24 +35,17 @@ _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
_DOWNLOADS = os.path.join(_ROOT, ".downloads")
|
||||
|
||||
|
||||
def _load_comment_cards(folder: str, dur: float, min_sec: float = 3.0,
|
||||
fixed: bool = False):
|
||||
"""지정 폴더의 이미지를 영상 길이에 맞춰 하단에 균등 배치.
|
||||
def _card_paths(folder: str):
|
||||
"""댓글 카드 이미지 경로 목록. 배치 방식과 무관하게 '순서'만 정한다.
|
||||
|
||||
장수: n = min(카드 수, max(1, floor(dur/min_sec))) — 카드 하나가 min_sec 밑으로
|
||||
내려가지 않는 상한. 배치 간격은 항상 dur/n → 카드가 모자라도 끝까지 빈 곳 없이
|
||||
채워지고(간격이 3초 이상으로 늘어남), 넘치면 초과분은 버린다.
|
||||
fixed=True 면 늘리지 않고 min_sec 고정 — 모자라면 뒤는 비운다
|
||||
(편집하면서 부분삭제를 많이 하는 경우 카드가 늘어나 있으면 타이밍이 꼬여서).
|
||||
정렬 규칙:
|
||||
- 모든 파일명이 숫자로 시작하면 → 숫자순(1, 2, 10 …)
|
||||
- 아니면 → 저장(생성) 순서 = 다운로드한 순서
|
||||
folder 가 비었거나 없으면 [] (댓글 카드 없음).
|
||||
Returns: [(start, end, path)]
|
||||
folder 가 비었거나 없으면 [].
|
||||
"""
|
||||
import re
|
||||
folder = (folder or "").strip().strip('"')
|
||||
if not folder or not os.path.isdir(folder) or dur <= 0:
|
||||
if not folder or not os.path.isdir(folder):
|
||||
return []
|
||||
imgs = [os.path.join(folder, f) for f in os.listdir(folder)
|
||||
if os.path.splitext(f)[1].lower() in (".png", ".jpg", ".jpeg", ".webp")]
|
||||
@ -67,7 +60,26 @@ def _load_comment_cards(folder: str, dur: float, min_sec: float = 3.0,
|
||||
imgs.sort(key=_leadnum) # 파일명 숫자순
|
||||
else:
|
||||
imgs.sort(key=lambda p: os.path.getctime(p)) # 저장(생성) 순서
|
||||
return imgs
|
||||
|
||||
|
||||
def _load_comment_cards(folder: str, dur: float, min_sec: float = 3.0,
|
||||
fixed: bool = False):
|
||||
"""지정 폴더의 이미지를 영상 길이에 맞춰 하단에 균등 배치.
|
||||
|
||||
장수: n = min(카드 수, max(1, floor(dur/min_sec))) — 카드 하나가 min_sec 밑으로
|
||||
내려가지 않는 상한. 배치 간격은 항상 dur/n → 카드가 모자라도 끝까지 빈 곳 없이
|
||||
채워지고(간격이 3초 이상으로 늘어남), 넘치면 초과분은 버린다.
|
||||
fixed=True 면 늘리지 않고 min_sec 고정 — 모자라면 뒤는 비운다
|
||||
(편집하면서 부분삭제를 많이 하는 경우 카드가 늘어나 있으면 타이밍이 꼬여서).
|
||||
|
||||
⚠ 이 함수는 컷을 모른다. 자동 탭처럼 '어느 컷에 붙일지'가 정해진 경우엔
|
||||
`_cards_by_cut()` 을 쓴다. 폴더 지정 경로(파일/유튜브 구간 탭)는 계속 이 함수를 쓴다.
|
||||
Returns: [(start, end, path)]
|
||||
"""
|
||||
imgs = _card_paths(folder)
|
||||
if not imgs or dur <= 0:
|
||||
return []
|
||||
n = min(len(imgs), max(1, int(dur // min_sec)))
|
||||
if fixed: # 3초 고정 — 뒤가 비어도 늘리지 않음
|
||||
return [(i * min_sec, min((i + 1) * min_sec, dur), path)
|
||||
@ -77,6 +89,40 @@ def _load_comment_cards(folder: str, dur: float, min_sec: float = 3.0,
|
||||
for i, path in enumerate(imgs[:n])]
|
||||
|
||||
|
||||
def _cards_by_cut(paths, card_cuts, placements, dur: float, *,
|
||||
min_sec: float = 3.0, fixed: bool = False):
|
||||
"""카드를 '소속 컷 구간 안'에 배치 — 자동 탭 전용.
|
||||
|
||||
card_cuts[i] = i번째 카드가 속한 컷 인덱스. 컷 안에서는 균등 분할
|
||||
(fixed=True 면 min_sec 고정, 컷 뒷부분은 비움).
|
||||
|
||||
왜 컷 단위인가: 전체 균등 배치(`_load_comment_cards`)는 컷 경계를 몰라서
|
||||
3번 컷 얘기하는 댓글이 7번 컷 위에 뜬다. 컷 안에서 계산하면 한 컷이 덜 차도
|
||||
**다음 컷 카드가 앞으로 밀리지 않는다.**
|
||||
Returns: [(start, end, path)] 시간순
|
||||
"""
|
||||
if not paths or not card_cuts or not placements or dur <= 0:
|
||||
return []
|
||||
groups: dict = {}
|
||||
for i, ci in enumerate(card_cuts[:len(paths)]):
|
||||
if isinstance(ci, int) and 0 <= ci < len(placements):
|
||||
groups.setdefault(ci, []).append(i)
|
||||
out = []
|
||||
for ci, idxs in groups.items():
|
||||
p0, p1 = placements[ci]
|
||||
p1 = min(p1, dur)
|
||||
if p1 <= p0:
|
||||
continue # 영상 실제 길이 밖 컷은 버린다
|
||||
step = min_sec if fixed else (p1 - p0) / len(idxs)
|
||||
for k, i in enumerate(idxs):
|
||||
s = p0 + k * step
|
||||
if s >= p1:
|
||||
break # fixed 로 컷을 넘치면 뒤는 비운다
|
||||
out.append((s, min(s + step, p1), paths[i]))
|
||||
out.sort(key=lambda t: t[0])
|
||||
return out
|
||||
|
||||
|
||||
def _elapsed_kept(keep_sorted, t: float) -> float:
|
||||
"""원본 시간 t 가 무음 제거 후(압축) 타임라인에서 놓이는 위치 = t 이전의 보존 길이 합."""
|
||||
tot = 0.0
|
||||
@ -311,6 +357,7 @@ async def process_paste(
|
||||
scene_split: bool = False,
|
||||
comments_dir: str = "",
|
||||
cards_fixed: bool = False,
|
||||
card_cuts: Optional[List[int]] = None,
|
||||
bg_white: bool = False,
|
||||
remove_silence: bool = False,
|
||||
asr_bottom: bool = False,
|
||||
@ -370,6 +417,11 @@ async def process_paste(
|
||||
eff_caps = [(p0, min(p1, dur), ef) for (p0, p1), (_, _, _, ef) in zip(placements, cuts)
|
||||
if ef and p0 < dur]
|
||||
|
||||
# 댓글 카드(자동 탭): '몇 번 컷 소속'만 받아 여기서 시간을 만든다.
|
||||
# 서버가 시간을 확정하면 무음 제거 때 자막만 당겨지고 카드는 혼자 어긋난다.
|
||||
cut_cards = _cards_by_cut(_card_paths(comments_dir), card_cuts or [],
|
||||
placements, dur, fixed=cards_fixed)
|
||||
|
||||
video_clips = [(0.0, dur)] # 병합본 = 한 덩어리(재컷 없음)
|
||||
timeline_dur = dur
|
||||
|
||||
@ -384,6 +436,7 @@ async def process_paste(
|
||||
keep = sorted(keep)
|
||||
bottom_caps = _remap_caps(bottom_caps, keep)
|
||||
eff_caps = _remap_caps(eff_caps, keep)
|
||||
cut_cards = _remap_caps(cut_cards, keep)
|
||||
video_clips = keep
|
||||
timeline_dur = sum(e - s for s, e in keep)
|
||||
yield {"type": "log",
|
||||
@ -437,10 +490,12 @@ async def process_paste(
|
||||
video_clips = split_clips_at_scenes(video_clips, scenes)
|
||||
yield {"type": "log", "msg": f"장면전환 {len(scenes)}곳 → 세그먼트 {len(video_clips)}개"}
|
||||
|
||||
# 댓글 카드(선택): 지정 폴더의 1,2,3… 을 3초씩 하단에 순서대로
|
||||
cards = _load_comment_cards(comments_dir, timeline_dur, fixed=cards_fixed)
|
||||
# 댓글 카드: 컷 소속이 지정됐으면 그 컷 구간 안(위에서 계산), 아니면 폴더 균등 배치
|
||||
cards = cut_cards or _load_comment_cards(comments_dir, timeline_dur, fixed=cards_fixed)
|
||||
if cards:
|
||||
yield {"type": "log", "msg": f"댓글 카드 {len(cards)}개 하단 삽입(3초 간격)"}
|
||||
yield {"type": "log",
|
||||
"msg": f"댓글 카드 {len(cards)}개 하단 삽입"
|
||||
+ ("(컷별 배치)" if cut_cards else "(전체 균등)")}
|
||||
|
||||
path = await asyncio.to_thread(
|
||||
lambda: build_bg_template_draft(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user