조각을 화면에 얹는 방식이라 스크롤이 한 번도 일어나지 않았다. 심규선 상세페이지 실측 — 19조각 중 1920px를 넘는 것이 0개라 scroll_scene의 span이 항상 0이었다. 화면의 13~44%만 조각이고 나머지는 블러 포스터로 남았으며, 같은 정지컷이 6초 넘게 이어졌다. 정본(tech_spec §5)에 있던 extend_to_height가 이식에서 빠진 것이 원인이다. 그것이 없으니 조각이 짧을 때 블러 배경에 상단 정렬로 얹는 수밖에 없었다. - to_surface: 롱이미지를 폭 1080으로. trim_blank를 태우지 않는다 — 위를 깎으면 섹션 y좌표가 전부 어긋난다 - extend_to_height + blank_band: 글자 없는 배경 띠를 거울 타일링, 이음매 페더 40px. 프레임보다 짧아도 풀프레임을 채운다 - scroll_scene: 배경 합성 없이 surface.crop() — 블러가 섞일 여지를 없앤다 - build_scene_plan: 섹션은 훑기 시작점, 큐 길이가 훑는 거리. 섹션이 떨어지면 앞 씬이 멈춘 자리에서 이어 읽는다(커서) - SCROLL_TAG_PRIORITY에서 notice·crossbanner 제거 — 약관을 읽히는 영상이 아니다 - final_checks: 씬 종류가 scroll이라고 적힌 것이 아니라 실제 이동 거리를 본다. 이 검사가 물러서 0픽셀 스크롤이 게이트를 통과했다 실측(심규선 자산 전체 합성, 569프레임 23.72초): 콘텐츠 비율 t=8s 23%→99% · t=11s 13%→92% · t=17s 44%→99% 프레임 간 변화량 0.01(정지) → 22~31 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
40 lines
1.2 KiB
Python
40 lines
1.2 KiB
Python
from dataclasses import dataclass
|
|
from typing import Literal
|
|
|
|
from PIL import Image
|
|
from pydantic import BaseModel
|
|
|
|
SceneKind = Literal["clip", "stills", "scroll", "poster"]
|
|
|
|
|
|
class Scene(BaseModel):
|
|
"""나레이션 큐 하나에 대응하는 화면."""
|
|
cue: int
|
|
kind: SceneKind
|
|
section: str | None = None # scroll 시작점이 된 섹션 이름
|
|
tag: str | None = None # 그 섹션의 태그. 왜 골랐는지 남긴다
|
|
# scroll은 조각이 아니라 롱이미지 위에서 일어난다. 아래 셋이 그 좌표다
|
|
source: str | None = None # 훑을 롱이미지 (detail_01 …)
|
|
y0: float | None = None # 시작 위치. 롱이미지 높이 대비 0~1
|
|
y1: float | None = None # 끝 위치
|
|
|
|
|
|
class LongcutPlan(BaseModel):
|
|
scenes: list[Scene]
|
|
|
|
|
|
class EndBandInfo(BaseModel):
|
|
"""엔드밴드 두 줄. 위가 공연명, 아래가 일시·장소."""
|
|
title: str
|
|
detail: str
|
|
|
|
|
|
@dataclass
|
|
class LongcutResult:
|
|
video: bytes
|
|
thumbnail: bytes # 첫 프레임. 0초가 포스터 원본이라 그대로 정지컷이 된다
|
|
duration: float
|
|
frames: int
|
|
plan: LongcutPlan
|
|
proof: Image.Image | None = None
|