11 KiB
known_issue.md
이슈는 파이프라인·단계별로 묶는다. 제목에 표시가 없으면 축제 숏폼이다.
- 축제 숏폼: ① detect ② narration_text ③ motion ④ tts ⑤ bgm ⑥ i2v ⑦ render
- Playreel 롱컷: ① fetch ② split ③ upscale ④ analyze ⑤ motion ⑥ narration ⑦ tts ⑧ bgm ⑨ i2v ⑩ hybrid ⑪ compose ⑫ review
두 파이프라인은 motion·tts·bgm·i2v를 같은 컴포넌트로 공유한다. 어느 단계에도 속하지 않으면 공통에 둔다.
① detect
recommended_mode — 죽은 필드
models/detect.py의 Regions.recommended_mode는 현재 아무 데서도 읽히지 않는다.
detect.py가 title_style == "calligraphy"이면 layered, 아니면 camera로 채우기만 한다.
원본 리포에는 렌더 경로가 둘 있었다.
layered— 포스터를 레이어로 분해해 제목 글자는 원본 픽셀 그대로 유지하고 배경만 움직인다. 붓글씨는 획이 조금만 흐트러져도 글자가 아니게 되므로 calligraphy를 이쪽으로 보냈다.camera— 포스터를 통째로 두고 카메라만 움직인다(팬·줌).
이식한 export에는 둘 다 없다. 레이어 렌더러는 애초에 제외됐고, 실제 렌더는 생성형 i2v이며
그 프롬프트는 오히려 Locked-off camera: the poster never moves로 카메라 이동을 금지한다.
판단 유보로 필드는 남겨둔다. 레이어 경로를 되살리지 않기로 하면 지운다.
GridBox 좌표 순서가 강제되지 않는다
answers/detect_answer.py의 GridBox는 x0 < x1, y0 < y1을 보장하지 않는다.
프롬프트에도 순서 조건이 없고 스키마에도 밸리데이터가 없다.
VLM이 뒤집어 답하면 detect.py의 tighten_box()가 right <= left or bottom <= top에 걸려
None을 돌려주고, 호출부는 VLM 원값을 그대로 쓰는 fallback으로 떨어진다.
그 결과 x0 > x1인 Box가 그대로 만들어지고, check_overlay()의 draw.rectangle에서
PIL이 예외를 낸다.
원본 CLI(detect_regions.py)에도 같은 구멍이 있다.
막으려면 프롬프트에 순서 조건을 넣거나 GridBox에 밸리데이터를 단다.
focal_points를 VLM이 실제 지점이 아니라 멋대로 잡는다
focal_points 좌표가 포스터를 보고 잰 값이 아니라 그럴듯하게 배치한 값으로 나온다. 관측된 형태는 균등 간격·동일 높이지만, 원인이 같다면 다른 양상으로도 나올 수 있다.
이유는 아마도 둘이다.
- 검증이 없다. 박스는 VLM이 대충 답해도
tighten_box()가 잉크 경계로 다시 잡아준다. focal_points는 그 후처리가 없어 VLM 값이 곧 최종값이다. - 틀릴 수 없는 답이다. 박스와 달리 focal은 정답 경계가 없어서, 모델이 지어내도 스키마도 후처리도 걸러내지 못한다.
프롬프트로 격자를 읽게 못박거나, focal도 박스로 받아 후처리를 태우는 방향이 있다. 후자는 잉크 기반이라 글자에는 듣고 그림에는 덜 듣는다.
② narration_text
models/narration.py가 없다
generate_narration()이 answers의 NarrationAnswer를 그대로 반환한다.
detect와 달리 후처리가 없어 LLM 응답이 곧 결과이고, 지금 모델을 만들면 필드를 그대로
베끼는 껍데기가 되기 때문이다.
대신 answers 타입이 이 단계의 공개 반환 타입이 되어 LLM 레이어가 위로 새어나간다.
API 레이어가 다른 형태를 요구하면 그때 models/narration.py를 만들고 변환을 넣는다.
metadata에서 괄호 내용이 사라진다
concert_poster_image 실측 — 포스터 표기와 metadata가 다르다.
| 필드 | 포스터 | metadata |
|---|---|---|
| date_text | 2023. 1. 26(THU) 7:30PM |
2023. 1. 26THU 7:30PM |
| place | 영도문화예술회관 봉래홀(대공연장) |
영도문화예술회관 봉래홀 |
둘 다 괄호가 걸렸다. 나레이션에만 걸리는 금지문자 규칙(( ) 금지)을 모델이 metadata까지
적용한 것으로 보인다. 밸리데이터는 나레이션만 검사하므로 그대로 통과한다.
영상에는 영향이 없다. date_text·place는 아카이브 메타태그일 뿐 TTS도 렌더도 쓰지 않는다. 문제는 신호 쪽이다 — 모델이 표기를 그대로 옮기지 않고 "정리"하고 있다는 뜻이고, 같은 메커니즘이 숫자·지명에 걸리면 연도 오독(2026→2036)처럼 조용히 틀린 값이 아카이브에 남는다.
프롬프트에서 metadata는 금지문자 규칙 대상이 아니며 괄호까지 그대로 옮기라고 못박아야 한다.
⑤ bgm
Suno가 요청한 길이를 무시하고 풀 트랙을 준다
festival_poster_image 실측 — [Song Duration: Around 15 seconds]를 넣었는데 128초짜리가
왔다. 트랙 선택은 목표 길이에 가장 가까운 것을 고르지만, 후보가 전부 길면 소용이 없다.
⑦이 그 트랙의 앞부분을 영상 길이만큼 잘라 쓴다. 어디가 쓸 만한 구간인지는 보지 않으므로 인트로가 걸리면 그대로 인트로가 깔린다.
⑥ i2v
CLI와 HTTP API가 서로 다른 모델을 준다
이식한 코드는 higgsfield CLI를 서브프로세스로 부른다. CLI 토큰 인증이라 머신마다 사람이
로그인해야 하고 컨테이너 배포가 막힌다. 공식 SDK(higgsfield-client)는 API 키를 쓰므로
그 문제가 없고 업로드도 presigned URL이라 로컬 파일이 필요 없다.
그런데 openapi.json 확인 결과 API에 Kling 3.0이 없다. 2.1과 2.5-turbo뿐이다.
| CLI | HTTP API | |
|---|---|---|
| 모델 | kling3_0 | kling-video 2.5-turbo까지 |
| duration | 정수 자유 (8 사용) | 5 또는 10만 |
| aspect_ratio | 지정 가능 | 파라미터 없음 |
| sound | off 지정 가능 | 파라미터 없음 |
| 비용 사전조회 | generate cost |
없음 |
duration 5/10이 걸림돌이다. 파이프라인 전체가 8초 고정을 전제로 한다 — ②의 "7초 안에 읽혀야 한다", ④ 타임라인, ⑤ BGM 길이가 모두 거기서 나왔다.
aspect_ratio·sound는 실질 문제가 아닐 수 있다. Kling은 9:16을 지정해도 입력 이미지 비율로 출력하고, 오디오는 렌더에서 새로 얹는다.
비용 사전조회가 없는 것은 실질 손실이다. max_credits 오설정 방어가 사라진다.
결정: CLI를 유지한다. 8초와 Kling 3.0을 지키는 쪽을 택했고, 서브프로세스·머신별 로그인· 컨테이너 제약은 그 대가로 안고 간다. API가 8초나 Kling 3.0을 지원하면 그때 다시 본다.
배포에 Node 런타임이 딸려온다
@higgsfield/cli가 npm 패키지라 파이썬 이미지에 바이너리 하나 때문에 Node를 통째로 넣어야
한다. 이미지 안에서 higgsfield auth login을 사람이 할 수 없으므로
~/.config/higgsfield/credentials.json을 볼륨이나 시크릿으로 주입하는 방식이 된다.
토큰 만료·계정 전환을 배포 파이프라인이 감당해야 한다.
CLI 버전을 고정하지 않았다
npm i -g @higgsfield/cli는 항상 최신을 깐다. utils/higgsfield.py가 --json을 주고도
정규식으로 stdout을 긁는 이유가 원본 주석에 있다 — "--json이 평문을 돌려줄 때가 있다".
출력 포맷이 바뀌면 parse_result_url과 parse_credits가 함께 깨진다.
계정 가드는 CLI 경로에만 필요하다
utils/higgsfield.py의 assert_account()는 CLI 토큰이 조용히 바뀌어 남의 크레딧이 빠지는
사고를 막는 장치다. API 키로 가면 키가 곧 계정이라 이 가드 자체가 불필요해진다.
higgsfield_account 설정도 같이 없어진다.
생성부를 실행 검증하지 못했다
이 머신에 higgsfield CLI가 없다. result_url·credits 정규식 파싱은 원본을 옮겼을 뿐
실제 CLI 출력으로 확인한 것이 아니다.
[Playreel] ③ upscale
CLI가 파일 경로만 받아 임시 파일이 생긴다
utils/higgsfield.py를 타는 두 곳이 파일 경로를 요구한다 — 업스케일의 --image와
i2v의 --start-image. 나머지 단계는 전부 bytes로 도는데 여기서만 디스크에 닿는다.
upscale_poster()는 bytes를 받으면 NamedTemporaryFile로 잠깐 내렸다가 지운다.
animate()는 아예 Path만 받아 호출측에 떠넘긴다 — 두 곳의 처리가 다르다.
CLI를 계속 쓰는 한 없앨 수 없다. Higgsfield HTTP API로 가면 presigned URL 업로드라 파일이 필요 없지만, 그쪽은 8초와 Kling 3.0을 못 준다(⑥ i2v 항목 참조).
원본은 업스케일 결과를 받아오지 않는다
원본 stage_upscale은 CLI에 출력 경로를 주지 않고 곧바로
upscale/topaz_textrefine_2x.png가 있는지 검사한다. 그 파일을 만드는 코드가 없다.
animate_poster.py처럼 result_url을 파싱해 내려받는 단계가 빠진 것으로 보인다.
우리 upscale_poster()는 i2v와 같은 방식으로 result_url을 받아 bytes로 돌려준다.
크레딧 2가 상수로 박혀 있다
CLI generate create 응답에 크레딧 필드가 없어서(⑥ i2v에서 확인) 원본이 쓰던 값 2를
그대로 상수로 뒀다. 요금이 바뀌면 조용히 틀린 값이 기록된다.
[Playreel] ⑥ narration
나레이션이 템플릿이라 작품이 달라도 문장이 같다
7문장 중 다섯이 고정 문자열이고, 값이 들어가는 자리는 공연명·장르·출연진·기간·장소뿐이다.
축제 3문장은 VLM이 포스터를 보고 쓰는데 여기는 meta를 문자열에 끼워 넣는다.
슬롯 이름과 내용도 어긋난다. 수상 자리에 장르 문장이, 넘버 자리에 출연진 문장이 들어간다.
원본도 같다. playreel_pipeline._narration_lines가 문장까지 동일하다.
다만 원본에는 데모용 FAKE_LINES가 따로 있고 거기에는 "브로드웨이가 사랑한 무대가
서울에 옵니다", "렛 잇 고, 그 순간을 눈앞에서" 같은 문장이 손으로 적혀 있다.
프론트 목 데이터도 그 톤이다. 슬롯 이름이 수상·넘버인 이유가 그것이고,
그 문장을 만들어내는 코드는 원본에도 없다.
재료는 이미 있다. ② split이 synopsis·cast·still·discount 태그로 잘라두고
이미지도 blob에 있다. extract_cast가 cast 섹션을 VLM에 물어보는 방식을 그대로 쓸 수 있다.
[Playreel] ⑪ compose
스크롤 범위를 사람이 정하는 경로가 없다
원본은 compose_long.py의 SCENES[slug]["plan"]에 손으로 적었다. 세 작품만 있고
값은 전부 위쪽 일부만 훑는다 — 0→0.30, 0→0.14, 0→0.40, 0→0.10.
프론트에도 입력이 없다. 게이트 ①의 FetchGate가 고르는 것은 어떤 섹션을 쓸지(sections)뿐이고
스크롤 범위는 화면에 없다. final_confirm의 "상세페이지 풀프레임 스크롤"은 완성본 확인용
체크 항목이다.
우리는 읽는 속도로 자동 계산한다. 큐 길이 동안 화면 높이의 정해진 배수만큼 스크롤하고, 이미지가 길면 위쪽만 보여준다. 사람이 "여기부터 저기까지"를 고르는 경로는 없다.
배수 값이 검증된 게 아니다. 원본이 손으로 고른 구간과 결과가 다를 수 있다.