廣告管線은(는) 무엇을 하나요?
웹 프로젝트를 프로모션 영상이나 소셜 광고로 바꿔줍니다. 핵심 인사이트: 앱이 스스로 영화처럼 연출된다면 화면을 녹화하고, '생성'하지 마세요. 이미지 프로젝트라면 엄선된 이미지를 다운로드하여 Ken Burns 몽타주를 만드세요. 나머지(내레이션 / 음성 검증 / 합성 / 브랜드 / CTA)는 두 모드가 공유합니다. 실전 검증: battlefield-editor/tools/trailer(화면 녹화) + catime 광고 '세상을 읽는 고양이'(Ken Burns + 뉴스→이미지 공개) + '글리치와 블랙홀 씨' 전 7장 전체 플레이(화면 녹음의 유성 변형, 60분, 700개 대사 자동 진행: https://youtu.be/J9OMebCjr9Y ).
의사 결정 트리: 먼저 캡처 모드를 선택하세요
- 움직이는 앱(3D/canvas/자동 연출, auto-play 포함) → MODE A 화면 녹화(
capture-app.mjs). - 이미지 출력 / 갤러리 / 사진 피드(AI 생성 이미지, 갤러리) → MODE B Ken Burns 몽타주(엄선 이미지 다운로드 +
kenburns.sh). - 앱 자체에서 소리가 나는 경우(MODE A의 유성 변형; 무엇을, 얼마나 재생할지는 앱이 실시간으로 결정, 전체 오디오 트랙이 없음)
→
recordVideo를 포기하고 ffmpeg로 화면과 오디오를 동시에 캡처하세요. 자세한 내용은 '앱 자체에서 소리가 나는 경우: 실제 녹화' 섹션을 참조하세요. 전체 오디오 트랙이 있다면 이 방법을 사용하지 마세요. 사후에 먹싱하는 것이 음질이 더 좋습니다(GOTCHA 24). - 비시각적 저장소(CLI / 라이브러리 / API / 백엔드 / 데이터 파이프라인, 녹화할 화면 없음) → MODE C 터미널/해설(
term-player.html+capture-term.mjs): '명령어 + 실제 출력'을 타이핑 애니메이션 터미널 녹화로 만듭니다. Dev 도구는 '빠른 컷 예고편'이 아닌 '내레이션 교육 버전'이어야 합니다(GOTCHA 22 참조). 구조 = 문제점 hook → 단계별(설치→실행→출력 이해→고급 명령어) + edge-tts 내레이션으로 단계별 설명 + 단계 자막 → CTA. 내레이션 타이밍은term-player.html의window.__STEPLOG(각 단계 출력이 나타나는 초)를 사용하며, compose는 각 내레이션 줄을 해당 단계에 adelay합니다. 실전: wip 교차-저장소 상태 CLI 교육 영상(~60초, 배경 음악 볼륨 0.13으로 내레이션 위주). - 라이브 녹화의 명언 숏폼(9:16 Reels/Shorts) → MODE E 템플릿 녹화(
short-9x16.html+capture-short.mjs): course-pipeline이 먼저 대본에서 명언을 선별하고 16:9 클립을 자릅니다. 여기서는 그것을 9:16 HTML 템플릿에 삽입한 다음 녹화합니다. 왜 HTML을 사용하고 ffmpeg 필터를 사용하지 않는가?: 자르기는 ffmpeg로도 가능하지만, 키워드 애니메이션 효과는 CSS에서 10줄이면 끝나지만, ASS 자막 형식으로 동일한 효과를 구현하는 것은 지옥입니다. 레이아웃을 조정하는 것도 CSS를 한 번 보고 수정하면 되지만, filtergraph를 맹목적으로 수정해야 하는 것은 아닙니다. 경계: course-pipeline이 자르고, promo-video가 패키징합니다. 인터페이스는 '잘라낸 mp4 + 완성된 타임라인의 단어별 타임스탬프'입니다. - 혼합: screen-record를 본체로 하고 Ken Burns / 터미널을 보충 자료로 사용하는 것도 가능합니다.
4단계
- 캡처 — MODE A:
xvfb-run으로 headed Chromium 실행(WebGL 필요), PlaywrightrecordVideo로 자동 재생의 하이라이트 구간 녹화; MODE E는 headless로 진행(GOTCHA 47 참조). MODE B: 소스 목록 가져오기, 선별, 다운로드. - 내레이션(선택 사항) — edge-tts
zh-TW-YunJheNeural --rate=-5%로 내레이션 생성. - 검증(선택 사항) — Gemini pro가 대신 들어서 단어별 어조 검증; Gemini flash가 배경 음악 선별.
- 구성 —
brand.py로 브랜드 자료 생성(자막 카드/모서리 로고/CTA/QR), ffmpeg로 자막 오버레이/연결/오디오 믹싱, 여러 비율 + 요약 버전 출력.
여기 있는 여섯 개의 템플릿(상단 config / 매개변수만 바꾸면 사용 가능)
brand.py— PIL:cover/contain 비율 유지 합성기, 자막 카드, 상시 모서리 로고, CTA 카드, 미니 갤러리, QR(API 사용).kenburns.sh— 떨림 없는 Ken Burns 클립 + 텍스트가 그림자에 닿지 않도록 + concat + 오디오 믹싱(MODE B 주력).capture-app.mjs— Playwright+xvfb 화면 녹화(MODE A); 내장 http 서버, HUD 숨기기, 노드 클릭으로 구간 이동 / 키보드로 게임 구동.term-player.html+capture-term.mjs— MODE C 터미널/해설: HTML 터미널 플레이어(타이핑 애니메이션+컬러 출력) + xvfb 녹화. asciinema/xterm 불필요.short-9x16.html+capture-short.mjs— MODE E:9:16 템플릿(슬라이드 중앙 정렬, 상하 대자막, 명언 글자 확대 및 진동, 상시 모서리 로고, 진행 표시줄) + xvfb 녹화 + 사후 원본 오디오 먹싱. 슬라이드 한 칸은 자동 연출: 화면 자체에서 글자의 위치를 측정하여 2-3초마다 구도를 바꾸고, 키워드가 읽히는 순간에 클로즈업 및 밝기 증가(GOTCHA 32-37). 재녹화 시--tag v2로 파일명을 변경하고, 이전 완성본을 덮어쓰지 마세요. 이전 영상이 비교 기준입니다. 녹화가 완료되면 자동으로 '밝기 이벤트 횟수/초'와 '완성품의 음량'을 검증하므로, 별도로 확인할 필요가 없습니다.relevel.py— 이미 출시된 영상의 음량을 보정할 때 사용: 오디오만 수정하고,-c:v copy로 비디오 스트림은 그대로 유지합니다. 실측 결과 전후 비디오 스트림의 md5가 동일하며, 1초면 완료되므로 재녹화할 필요가 없습니다. 새로 녹화하는 영상에는 필요 없습니다(capture-short.mjs가 먹싱 시 이미 보정함).audition.py— Gemini가 대신 듣기:music "<용도>" *.mp3(배경 음악 선별 fit 0-10),tone "<텍스트>" v.mp3(내레이션 어조 검증).
'앱 자체에서 소리가 나는 경우': 실제 녹화
GOTCHA 24는 전체 오디오 트랙이 있는 경우(음악 비디오, edge-tts 내레이션)에 대해 설명합니다: 원본 파일을 사후에 먹싱하는 것이 음질이 가장 좋습니다. 하지만 한 가지 경우는 그렇게 할 수 없습니다——무엇을, 얼마나 재생할지는 앱이 실시간으로 결정하는 경우입니다. 실전: 비주얼 노벨의 전체 플레이, 700개의 더빙 파일이 스토리 순서대로 재생되며, 정렬할 수 있는 '전체 오디오 트랙'이 전혀 없습니다.
그렇다면 Playwright의 recordVideo를 완전히 포기하고, ffmpeg로 화면과 오디오를 동시에 캡처하세요:
# 1. 별도의 오디오 수신 단자를 열고, 브라우저의 사운드를 거기로 보내고, 데스크탑의 다른 소리는 섞지 않음
MOD=$(pactl load-module module-null-sink sink_name=cap)
# 2. 자체 X 화면
Xvfb :77 -screen 0 1280x808x24 -nolisten tcp &
# 3. 화면 + 사운드를 함께 녹화, 분할 파일로 저장(중단되어도 한 부분만 손실)
ffmpeg -f x11grab -draw_mouse 0 -framerate 25 -video_size 1280x808 -i :77.0 \
-f pulse -i cap.monitor \
-vf crop=1280:720:0:88 \
-c:v libx264 -preset veryfast -crf 24 -pix_fmt yuv420p \
-c:a aac -b:a 160k \
-f segment -segment_time 600 -reset_timestamps 1 seg/part-%03d.mp4 &
# 4. 브라우저 구동(DISPLAY=:77 PULSE_SINK=cap), 완료 후 concat으로 연결
5가지 실제 테스트에서 발견한 점:
--kiosk는 Playwright에 적용되지 않습니다, Playwright가 자체적으로 창을 관리합니다. 탭 표시줄과 주소 표시줄이 고정 88px이므로, 화면을 88px 더 크게 열고crop으로 잘라냅니다. 커서는-draw_mouse 0으로 숨깁니다.- 페이지 넘김에 고정 시간을 사용하지 마세요. 각 대사 길이가 다르므로, 너무 짧게 기다리면 끝부분이 잘리고, 너무 길게 기다리면 전체 영상 길이가 두 배로 늘어납니다.
new Audio()를 가로채서, 음성이 끝난 후에야 다음 장을 클릭합니다(해당 요소들은 DOM에 없어서querySelector로 찾을 수 없습니다). - '내용이 진행되었는지' 확인하는 검사가 반드시 있어야 합니다. 그렇지 않으면, 클릭할 수 없는 카드를 만나면 계속해서 빈 클릭을 하게 되고, ffmpeg는 어떤 오류도 보고하지 않습니다: 실제로 58분 동안 녹화했고, 파일은 정상이었지만, 결국 책의 4분의 1만 진행된 것을 발견했습니다. 판단 기준은 '화면의 텍스트가 변경되었는지'이며, 텍스트가 비어 있는 경우도 진행되지 않은 것으로 간주해야 합니다——종료 화면에는 텍스트가 없으며, 이 조건을 놓치면 추가로 33분이 더 녹화됩니다.
- 분할 파일 저장(
-f segment), 중간에 중단되어도 한 부분만 손실되며, 마지막에concat -c copy로 무손실 연결합니다. - 완료 후 네 가지를 확인해야 합니다. 파일이 생성된 것만으로는 안 됩니다: 전체 길이, 몇 장의 카드를 거쳤는지, 샘플링한 프레임의
표준 편차(0에 가까우면 = 빈 화면),
volumedetect의 평균 볼륨(무음은 null sink가 연결되지 않았음을 의미하며, 역시 오류를 보고하지 않습니다).
실전: glitch-vn/tools/capture/(record.sh + play_through.mjs). 완성품은
'글리치와 블랙홀 씨' 전 7장 전체 플레이, 60분, 700개 더빙, 한 번에 모두 진행되며 옆에서 누르는 사람이 없음:
https://youtu.be/J9OMebCjr9Y
GOTCHAS — 이 부분이 가장 가치 있는 자산입니다. 매번 이것을 확인하세요
-
비율 유지: 절대로 혼합 비율의 이미지에
scale=W:H를 사용하지 마세요(늘어나거나 찌그러집니다). 배경은 cover(비율 유지하며 확대 후 자르기), 본체는 contain(비율 유지하며 축소하여 배치)을 사용하세요. ffmpeg의 scale은 정수 배수(2x)이거나force_original_aspect_ratio를 추가해야만 합니다. 완료 전에 '원본 비율 vs 표시 비율' 감사를 실행하세요(오차 <0.1%여야 통과). -
Ken Burns 떨림: 원본을 먼저 2x 확대한 후 축소(정수 픽셀 점프가 서브 픽셀로 변경됨) + 확대/축소에 선형 프레임 번호
z='1+K*on/(D-1)'사용, 절대로 누적 반올림이 발생하는z='min(zoom+inc,...)'사용 금지. -
zoompan 프레임 폭발:
-loop 1 -t사용 금지(각 입력 프레임이 ×d로 곱해짐); 단일 이미지-frames:v D+zoompan d=D. -
텍스트가 그림자에 닿지 않도록: 이미지를 먼저 세로 중앙 정렬; 텍스트 위치 = 중심 ± (이미지 높이 × zmax)/2 ± 간격, 이미지별로 계산(PIL이 각 이미지 높이를 sidecar로 출력). 이렇게 하면 가로 이미지가 너무 멀리 떨어지지도 않고, 눌리지도 않습니다.
-
file:// fetch 차단: 앱이
fetch(?pkg=)/JSON 로드 →python3 -m http.server로 http 실행(또는 capture-app.mjs 내장 서버 사용). -
headless WebGL에서 swiftshader 누락/완전 검은 화면 →
xvfb-run -a --server-args="-screen 0 WxHx24"로 headed Chromium 실행. -
ES-module 앱:
gotoPhase가 window에 없음 → DOM으로 타임라인 노드#tlNodes .node[i].click()를 클릭하고,evaluate(gotoPhase)사용 금지. -
QR:PEP668로 qrcode를 설치할 수 없음 →
api.qrserver.com/v1/create-qr-code/?data=<공개 링크>(공개 링크만 사용); 출시 전에 휴대폰으로 한 번 스캔하여 확인. -
edge-tts 파자: 먼저 회피('没 mò'→단어 변경하여 피함); 어조 검증은 Gemini pro에 음높이 방향을 질문, whisper STT는 어조를 검증할 수 없음(잘못 읽어도 동일한 문자로 변환됨).
-
배경 음악:Gemini flash가 CC0 선별, 분위기에 맞는지 확인(전쟁 음악을 고양이에 적용하지 마세요); CC0 출처 기록.
-
다중 비율:16:9=YouTube, 9:16=Reels/Shorts/LINE, 1:1 & 4:5=FB/IG 피드. MODE A는 각 비율별로 네이티브 재녹화(세로 viewport); MODE B는 각 비율별로 재합성.
-
상시 모서리 로고(채널 로고):브랜드+URL이 전체 영상에서 화면 모서리에 위치(끝 카드뿐만 아니라), 전체 영상 위에 투명 PNG 한 장을 오버레이.
-
광고는 행동을 유도해야 함:처음 2초는 후크(음소거 자동 재생, 첫 프레임부터 화면/큰 글자가 보여야 함), 자막 내장(FB 음소거), CTA에 '지금 바로' 할 이유를 제시.
-
플랫폼 세부 사항:FB 영상은 게시물에, 링크는 첫 번째 댓글에(도달률 저하 방지);
+movflags +faststart; LINE 임베디드 재생은 앨범/영상 버튼으로 전송해야 하며, '파일'로 전송하면 안 됩니다. -
스크린샷은 로딩 완료 후 + 각각 빈 화면이 아닌지 확인:MODE A/gallery 항목 스크린샷 고정 시간 사용 금지(느린 것은 '로딩 중' 빈 화면을 캡처할 수 있음). 로딩 표시기가 사라질 때까지 폴링한 후에 캡처; 캡처 후 각각 목표 영역의 stdev 측정(≈0 = 빈 화면/렌더링 안 됨), 자동으로 누락 감지 및 재캡처. 실전: roll-formosa Miaoli가 고정 1.8초로 공백 캡처, wait-for-load + stdev 스캔으로 복구.
-
실제로 확대하여 확인하고, 썸네일만 믿지 마세요:검증 시 반드시 전체 해상도 단일 프레임을 자세히 확인(자르기/흐릿한 가장자리/잘림은 썸네일에서 보이지 않음). 좌표를 측정할 때 원본 이미지에 y/x 격자를 그리세요. 실전: 랜드마크 하단이 55px 잘리고, 하반부가 흐릿하게 나타났으며, 모두 썸네일에서는 보이지 않고 확대하여 격자를 그려야 발견할 수 있었습니다.
-
인터랙티브 게임에 auto-play 없음 → 키보드로 직접 플레이(MODE A의 하위 모드):Katamari/액션 게임은 Playwright
keyboard.down/up으로 구동(방향키로 굴림). 게임은 로드 후 ~10초(precache/PREPARING) 후에야 실제로 시작됨: 먼저 wait-until-START(버튼 준비 상태 폴링)→ START 클릭 → 키 입력 전송. 블라인드 봇이 반드시 무언가를 먹거나 성장하는 것은 아님(roll-formosa 가로 모드는 2cm에서 멈춤)——'가장 화면이 좋은' 창(공이 충분히 크거나/객체가 많음)을 캡처하거나, 다음 쇼케이스로 대체. -
gameplay 촬영이 어려움 → 자동 쇼케이스/gallery를 자료로 사용:많은 앱에 '객체 도감/미리보기' 자동 페이지가 있음(예:
preview.html?city=X), 플레이하지 않고도 모든 콘텐츠를 렌더링하므로 신뢰할 수 있는 몽타주 소스임(roll-formosa 20개 도시 랜드마크가 이에 해당). 이러한 어두운 배경 객체는 순수한 어두운 배경을 사용하고, 배경을 흐리게 하지 마세요(그렇지 않으면 유령 이미지가 발생). -
'준비 완료' 화면이 녹화 후반부에 나타남:recordVideo는 로드부터 녹화를 시작하므로, PREPARING→START의 준비 완료는 후반부에 나타남; 충분히 길게 녹화(START까지 기다린 후 + 3초 더 정지)하고, 후반부에서 준비 완료된 부분을 트림하고, 처음부터 자르지 마세요(로딩 화면이 잘림).
-
휴대폰 세로 viewport가 더 나은 경우가 많음:게임의 모바일 레이아웃이 때로는 더 꽉 차거나, 심지어 동작이 다를 수 있음(roll-formosa 세로 gameplay에서는 공이 자라고, 가로 모드에서는 멈춤). 각 비율을 네이티브로 재녹화할 때, 어떤 viewport가 가장 좋은 성능을 보이는지 함께 확인하세요.
-
카피의 정확성:주인공/객체 소개는 개성/기벽을 사용하고 외형은 사용하지 마세요(catime 캐스팅); 광고의 '뉴스→이미지' 한 줄은 소스 자료의 아이디어/스토리 필드에서 추론하고, 임의로 상상하지 마세요(잘못된 뉴스를 연결하면 창피해집니다).
-
예고편 ≠ 교육, 형식을 혼동하지 마세요:시각적 프로젝트(게임/앱)는 15-20초 빠른 컷 예고편(tease, 관심 유도)에 적합함; 하지만 CLI/dev 도구를 빠른 컷 예고편으로 만들면 매우 지루하고 교육적인 느낌이 없음——~60-90초 내레이션 교육으로 만들어야 함: 문제점 hook →
설치→실행→출력 이해→고급단계별 + edge-tts가 단계별로 설명(천천히, 각 단계에서 충분히 정지) + 단계 자막 → CTA, 배경 음악 볼륨을 매우 낮게(~0.13) 설정하여 내레이션이 주가 되도록 함. 실전: wip 첫 번째 버전 빠른 컷이 지루하다는 평을 받아, 내레이션 교육 버전으로 변경한 후에야 '이해했고, 사용할 수 있겠다'는 실질적인 느낌을 얻음. 내레이션 타이밍은 term-player의__STEPLOG를 사용. term-player는 각 단계마다 화면을 지워야 함(GOTCHA 16의 동일 출처 문제: 내용이 누적되면 화면 밖으로 밀려나고, 설명되는 명령어가 밀려남). -
먼저 전체 README/
--help를 스캔하여 기능 목록을 만든 다음, 스크립트를 작성하세요:도구 교육/해설을 만들 때 처음 부분만 읽고 촬영을 시작하지 마세요——주요 기능을 놓칠 수 있음(wip에서dashboard.py시각적 대시보드,wip done,--root,install-skill, Codex 지원,see:계획 파일 감지 누락). 먼저 모든 기능을 나열한 다음, 시연할 기능을 선택하세요. TTS로 계정/handle/드문 철자를 억지로 읽게 하지 마세요(예:yazelin은 사실 중국어 이름 '야쩌린'의 병음이며, 한 글자씩 읽는 것은 이상함)——화면에 이미 URL이 있으면 내레이션으로 '링크는 아래에 있습니다'라고 간단히 언급하세요; 시작하기 어려운 단어는 피하세요('먼저…' → '…는 매우 간단합니다'). edge-tts로 일반 영어 용어(cargo/markdown/Claude Code)를 읽는 것은 오히려 괜찮으므로, Gemini flash로 한 문장을 들어 확인하세요. -
MODE D 음악 비디오:전체 노래 + 인터랙션, 녹화하지 말고, 사후에 원본 파일을 먹싱하세요. Playwright의
recordVideo는 오디오 트랙과 커서를 포함하지 않습니다(전체 오디오 트랙이 없는 경우 '실제 녹화' 섹션 참조). 따라서: (a) 사운드는 원본 mp3를 사후에 먹싱(음질이 가장 좋으며, xvfb의 null sink 영향을 받지 않음); (b) 가짜 커서를 주입(pointermove를 따라 움직이는 이미지를 한 장 붙이고, 녹화 전용이며 저장소에는 포함하지 않음). 그렇지 않으면 '마우스 상호 작용'이 영상에서 단지 효과가 저절로 움직이는 것처럼 보입니다. 액션 타임라인은audio.currentTime폴링(waitForFunction(t => au.currentTime >= X))에 바인딩해야 하고,waitForTimeout누적을 사용하지 마세요——녹화기가 프레임을 드롭하면 벽 시계가 흐르지만, 오디오 시계는 흐르지 않습니다. -
타이밍은 화면 속의 시계에 의존하고, node의
Date.now()에 의존하지 마세요. webm의 첫 프레임과newContext()사이에는 불확실한 차이가 있으며, Playwright는 끝부분에 많은 양의 정지 프레임을 추가합니다(실측 160초 콘텐츠가 239초 파일로 기록됨).ffprobe의 duration을 길이로 사용할 수 없습니다. 방법: 진행 표시줄 부분을 crop하고, 프레임별로 슬라이더의 x를 측정(가장 높은 분홍색 픽셀 열을 피크로 취한 다음 중심을 구함——지나가는 입자도 같은 색이므로 단순 평균을 내면 값이 왜곡됨),화면 시간 → 오디오 시간에 대한 선형 피팅을 수행하여 오디오 시작점을 해결합니다. 세 가지 추정 방법(슬라이더 피팅, 가사 줄바꿈 시점, 클릭 당시의 벽 시계)을 교차 검증하여 중앙값을 취함; 가사 줄은 CSS 전환으로 인해 약 0.1초 느리므로, 이 값을 빼야 합니다. 완료 전에 상호 상관을 사용하여 완성된 오디오 트랙과 원본 파일 간의 지연을 확인합니다(AAC 프라이밍으로 인해 일반적으로 약 40ms 고정 지연, 정상). 그런 다음 '줄바꿈에서 약간 떨어진' 세 개의 프레임을 추출하여 올바른 문장이 강조되었는지 확인합니다. -
세로(9:16)는 브라우저 확대/축소로 해상도를 높이려고 하지 마세요.
recordVideo는 'CSS 크기의 화면'을 원하는 프레임 크기에 붙여넣습니다: viewport 405×720에recordVideo.size1080×1920을 설정하면, 결과는 405×720 콘텐츠가 왼쪽 상단에 붙여지고 나머지는 회색으로 채워집니다;deviceScaleFactor를 사용하여 장치 픽셀을 높이려고 하면, Playwright의 viewport 시뮬레이션이 DSF를 1로 되돌립니다;--force-device-scale-factor+--window-size를 사용하세요. xvfb에는 창 관리자가 없으므로,--window-size,Browser.setWindowBounds,--kiosk는 모두 창 크기를 변경할 수 없습니다(innerWidth가 501로 고정됨).html{zoom:...}도 작동하지 않으며, media query는 여전히 원래 너비를 읽습니다. 실행 가능한 해결책: viewport를 네이티브 1080×1920(DSF 1)으로 직접 열고, '확대된 세로 스타일'을 주입합니다(모바일 버전의 레이아웃 + 글꼴 크기/컨트롤을 비율에 맞게 확대,!important로 데스크톱 버전의 grid를 재정의). 녹화 전용 스타일은 저장소에 포함되지 않으며,HIDE와 동일한 위치에 주입됩니다. 글자가 흐릿한지 여부는 확대하여 확인해야 합니다(GOTCHA 16): 405에서 1080으로 늘어난 글자의 가장자리는 부드럽고, 네이티브 렌더링은 선명합니다. -
가능하면 '화면이 스스로 말하게' 하세요:인터랙티브 유형의 녹화에서는, 시연할 작업을 타임라인으로 편성(클릭, 드래그, 정지)하고, 중간에 의도적으로 완전히 움직이지 않는 구간을 남겨서 자동으로 숨겨지는 UI가 사라지도록 하세요——계속 움직이면 오히려 시청자가 요점을 보지 못합니다. 인터랙션 녹화 시 recordVideo는 커서를 포함하지 않으므로, 가짜 커서를 주입하세요(pointermove를 따라 움직이는 이미지). 그렇지 않으면 시청자는 단지 효과가 저절로 움직이는 것처럼 봅니다.
-
타이밍 앵커는 '하얀 섬광'을 사용하고, '완전 검정'을 사용하지 마세요. 아이디어는 '페이지를 먼저 완전 검정으로 만들고, 비디오가 실제로 재생되기 시작할 때만 표시하며, 첫 번째로 밝아지는 프레임을 t=0으로 스캔한다'는 것입니다——실측 결과 성립하지 않음:Playwright의 recordVideo는 첫 번째 프레임부터 수집하지 않으며, 전체 녹화에서 가장 어두운 프레임은 24.4/255로, 인식할 수 있는 검은 프레임이 전혀 없습니다. 대신 재생 시작 전에 400ms 동안 완전 흰색을 표시하고, 재생이 시작되는 순간에 제거한 다음, '흰색 섬광이 끝난 다음 프레임'을 스캔하세요. 흰색 250은 콘텐츠의 24-37과 명확히 구분됩니다. 오프셋은 전체적으로 ±0.05초로 검증되었으며, 드리프트가 없습니다.
-
metadata=print는 기본적으로 stderr에 기록됩니다.ffmpeg -vf "...,metadata=print:key=X"의 출력이 stdout에 없으므로, 스크립트가 stdout만 수집하면 영원히 빈 값을 읽게 되고, '읽을 수 없음'을 '화면이 검은색'으로 잘못 판단합니다.:file=-를 추가하세요. -
내장 http 서버는 먼저 파일을 읽은 다음 헤더를 전송해야 합니다.
res.writeHead(200)이await readFile앞에 작성되면, 파일이 없을 때 200이 이미 전송되었고, catch에서 404를 보충하려고 하면ERR_HTTP_HEADERS_SENT가 발생합니다—— 실제 원인(경로 오류)이 이 가짜 오류로 가려집니다. 이 저장소의capture-app.mjs에는 원래 이 함정이 있었으며, 수정되었습니다. -
A/V 동기화 검증은 식별력 있는 측정 방법을 사용해야 합니다. '동일한 시점의 프레임을 추출하여 상관 관계 비교'는 합리적으로 들리지만, 슬라이드 형식의 콘텐츠는 수십 초 동안 화면이 바뀌지 않으므로, 음성 대조(의도적으로 6초 차이)도 0.996이 되어 검증 효과가 없습니다. 가능한 방법: 템플릿에 진행 표시줄(너비 =
currentTime / duration)을 배치하고, 완성된 영상에서 표시줄 너비를 측정하여 플레이어의 내부 시간을 읽어와서, 출력 시간과 직접 뺍니다. 이 표시줄은 완성된 영상의 시각적 요소로도 사용되며, 테스트를 위해서만 추가되는 것은 아닙니다. -
정지된 슬라이드의 9:16 숏폼 영상, 화면이 죽어 있습니다——그리고 측정 가능합니다. 슬라이드 한 칸의 프레임별 그레이스케일 차이는 실제로 0.072 / 0.092에 불과하며(정지 대조군 0.000), 사용자 피드백은 '너무 평범하고, 펀치감이 부족하다'입니다. 임계값을 감으로 정하지 말고, 직접 출시한 9:16 작품을 동일한 기준으로 측정하여 사용 가능한 범위를 찾으세요: neko-tensei 음악 비디오 0.233, k-rider 예고편 1.045, battlefield-editor 1.293, roll-formosa 1.912, catime 광고 3.965, catime 예고편 6.168(active_ratio 0.636 = 빠른 컷). 설명형 명언 숏폼은 빠른 컷으로 만들어서는 안 됩니다.
목표 범위 0.30 ~ 1.05, active_ratio 0.10 ~ 0.40——이 임계값 세트는 폐기됨:mean과active_ratio모두 프레임 레이트로 정규화되지 않았으며, 자신의 작품을 사용하여 자신의 임계값을 설정하는 것은 순환 논증입니다. 현재 판단 기준은punch-score.py의motion.slide.per_s ≥ 5.5(초당 그레이스케일 변화량,mean × fps, 프레임 레이트에 따라 변하지 않는 유일한 숫자), 상한은 설정하지 않으며, 위험한 '섬광'은 GOTCHA 35의 밝기 이벤트로 차단합니다. 근거: 사용자가 '화면이 죽었다'고 말한 v1은 1.79와 2.30이었으며, 가장 조용한 출시 작품인 neko-tensei는 5.82였고 반드시 통과해야 합니다. -
정지된 슬라이드의 카메라 움직임은 화면 자체에서 측정해야 하며, 고정 비율로 경도를 지정해서는 안 됩니다. 960×720 슬라이드의 실제 측정 결과 250개의 스캔 라인에만 텍스트가 있고, 나머지는 공백입니다; 고정된 상/중/하 자르기는 페이지 상단의 작은 글자 줄이나 완전한 공백을 자를 수 있습니다. 방법: canvas에서 한 프레임 추출 → 각 행의 잉크 양(배경 중앙값과의 차이) → 밴드로 연결 → 잉크 양이 12% 미만인 밴드 폐기 → 남은 밴드의 좌우 극값이 텍스트 범위 SP이며, 카메라는 아무리 움직여도 SP를 잘라서는 안 됩니다. 측정할 때 축소하지 마세요: 8배 축소(120×90)하여 측정하면,
drawImage의 축소판은 면적 평균이 아니며, 가는 획이 직접 사라짐(텍스트 오른쪽 끝의 '的路径。'를 측정할 수 없어 SP가 96px 부족하게 계산됨). 배경 그라데이션은 가로로 관통하는 것으로 간주됩니다. 임계값을 아무리 조정해도 문제가 해결되지 않았으며, 원본 해상도를 직접 사용하면 이 문제가 없었고, '잉크'는 검증과 동일한 정의(배경 중앙값보다 28 높음)를 사용할 수 있었습니다. 960×720을 한 번에 스캔하는 것은 headless Chromium에서 수십 밀리초밖에 걸리지 않습니다. -
구도 잡기는 '현재 재생 중인 프레임'을 측정해야 하며, 먼저 seek하여 측정하지 마세요. '명언이 말해지는 페이지'를 측정하려면
V.currentTime = 명언 중간然后drawImage——seeked는 디코딩 완료만 보장하고, 화면은 아직 바뀌지 않았습니다, 실제로 전체 quote-06 영상이 0초(이전 페이지)의 레이아웃을 사용하여 구도를 잡았습니다: SP가 596으로 측정되었지만 실제로는 696이었습니다. 따라서 '글자를 자르지 않음'을 보장하는 프레임이 처음부터 잘못되었고, 플래그십 키워드의 2.8초 동안 절반의 프레임에서 텍스트가 잘렸습니다. 신뢰할 수 있는 방법은 재생 중에 측정하는 것입니다: 시작 시 0초의 프레임 사용(그것이 당시 화면에 있던 것입니다), 이후 장면이 변경될 때마다 다시 측정합니다. 또한 '한 문장이 두 페이지에 걸쳐 있는' 문제도 해결됩니다——페이지 전환 후 다음 장면 변경 시 따라잡힙니다. -
명암 이벤트에는 측정 가능한 상한이 있어야 하며, '나는 매우 느리게 한다'고 말하는 것만으로는 충분하지 않습니다. 슬라이드 밴드의 프레임별 YAVG를 측정하고, 임계값은 상대 10%에 절대 하한 4/255를 추가합니다——슬라이드 배경색 Y가 약 20이므로, 순수 절대값을 임계값으로 사용하면 9% 밝기 증가도 감지할 수 없습니다; 순수 상대값을 사용하면 어두운 영역의 노이즈가 판을 칠 수 있습니다. '가장 밀집된 1초에 몇 번'을 세고, 3회를 유지합니다 (광과민성 간질의 위험 임계값). 실측 결과 완성품은 2회/초; 음성 대조 5Hz 깜박임 26회/초, 1Hz ±6% 느린 맥동 6회/초, 둘 다 차단 가능합니다. 이 검사는 이미
capture-short.mjs에 작성되어 있으며, 녹화 후 스스로 출력합니다. -
ffmpeg의eq필터, brightness 표현식은 기본적으로 한 번만 계산됩니다(eval=init). '의도적인 깜박임'을 위한 음성 대조를 만들 때:eval=frame을 누락하면, 필터가 작동하지 않으며, 측정 결과가 완성품과 동일합니다——거의 좋은 지표를 '식별력 없음'으로 판단할 뻔했습니다. 음성 대조 자체도 먼저 실제로 고장 났는지 확인해야 합니다, 그렇지 않으면 도구가 두 개의 동일한 파일을 구분할 수 없다는 것을 증명할 뿐입니다. -
프레임 차이는 '동기화'에 대해 항상 식별력이 없습니다. GOTCHA 31의 내용을 다른 템플릿에서 다시 검증: words.json을 의도적으로 6초 어긋나게 설정하면, 슬라이드 밴드의 프레임 차이는 동일한 0.718(words.json을 전혀 보지 않음)입니다. 폭발하는 것은 '자막 블록 변경 시점 vs 자막 영역 최대 프레임 차이 시점' 라인(0.091 → 0.567)입니다. 동적 지표는 동적임을 주장할 수만 있으며, 동기화는 별도로 측정해야 합니다.
-
자막 분할에서 '인접한 두 개의 키워드'를 만나면 하나의 블록으로 붙여버립니다. 분할은 '들어갈 수 없을 때'만 블록을 변경하며, 다른 키워드로 변경될 때도 여전히 들어갈 수 있는 경우가 많습니다——실제로 '큰 물음표' '검증되지 않음' 사이에 다른 글자가 없어서, 11자 블록 하나로 붙여지고, 블록 전체가 금색이 되어, 두 개의 폭발 지점이 하나로 줄어듭니다(밝은 블록이 2개만 남음). 수정 방법: 하나의 조건을 추가합니다:
k >= 0 && k !== 이전 글자의 k이면 강제로 새 블록을 엽니다. 수정 후 밝은 블록 3개, 최대 블록이 11자에서 6자로 줄어듭니다.punch-score.py에도 동일한 분할이 이식되어 있으므로 함께 수정해야 합니다. -
실행 중에 비밀리에 확대/축소가 곱해지므로, 구도를 계산할 때 먼저 그 여유를 빼야 합니다. 느린 푸시(+3.5%)와 키워드 푸시(+5%)를 합치면 8.7%: '딱 맞게' 잡은 구도가, 푸시되면 텍스트를 화면 밖으로 밀어내며, 특히 키워드가 있는 몇 초 동안 발생합니다——가장 문제가 없어야 할 부분에서 문제가 발생합니다. 두 가지를 모두 수행해야 합니다: 구도 잡기 시
z = 너비 허용치 / 1.087, 그리고 매 프레임마다 다시 클램프(z ≤ 너비 허용치,cx를 SP 양쪽 끝이 모두 프레임 안에 있도록 클램프). 불변량은 화면이 나가는 순간에 성립해야 하며, '구도를 잡을 때만 올바르게 계산된' 상태로는 안 됩니다. -
슬라이드 자체의 페이지 머리글과 바닥글이 일단 화면 중앙에 배치되면, 좌우가 반드시 잘리며, 텍스트가 잘린 것으로 계산됩니다. 페이지 머리글의 작은 글자 줄(x 24 ~ 932)과 페이지 바닥글의 진행 표시줄(x 0 ~ 759)이 모두 텍스트 범위 밖으로 나갑니다; 카메라가 푸시되면 화면 양쪽을 벗어납니다. 검증 시 슬라이드 밴드의 상하를 각각 10%씩 '페이지 머리글/바닥글'로 수정하면, 카메라 움직임이 변경될 때 요소가 해당 선의 안쪽에 위치하게 되어, 텍스트가 잘린 것으로 계산됩니다. 수정 방법: '잉크가 SP 밖으로 나가는' 모든 행을 요소로 표시(판단 기준은 '가로로 완전히 관통'이 아님——페이지 바닥글의 진행 표시줄은 잉크 임계값에서 79% 너비만 남으므로, 전체 너비로 판단하면 놓칠 수 있음), 그런 다음 카메라 중심을 제한하여 요소가 화면 상하 가장자리의 10% 이내에만 위치하거나 완전히 프레임 밖으로 나가도록 합니다. 경계는 몇 픽셀 더 여유를 두어야 합니다: 정확히 10%에 걸쳐 있으면, 샘플링의 반올림으로 인해 요소의 마지막 한두 행이 텍스트 쪽으로 떨어질 수 있습니다(실제로 15프레임이 남았으며, 모두 81, 82행에 있었습니다).
-
구도 문제를 진단하려면 먼저 알고리즘을 오프라인에서 다시 실행하고, 매개변수를 조정하여 시도하지 마세요. 한 번의 녹화가 40초일 때, 블라인드 테스트로 임계값을 조정하면 '점점 더 나빠지는' 방향으로 갈 수 있습니다: 실제로 느린 푸시와 키워드 푸시를 끄면(이것이 주된 원인이라고 생각), 글자 잘림이 102프레임에서 189프레임으로 증가했습니다. ffmpeg로 동일한 감지 프레임을 추출하고, Python에서 동일한 band/SP 로직을 실행하면, 10초 만에 '브라우저가 측정한 SP가 0초 페이지와 완전히 동일하다'는 것을 알 수 있으며, 주된 원인이 즉시 드러납니다. 또한 구도 잡기의 전제 조건을 로그에 출력(SP, 너비 허용치의 z, 요소 위치)——구도가 무너질 때, 이 세 숫자를 보면 너비를 잘못 측정했는지, 아니면 통로가 막혔는지 알 수 있습니다.
-
완성품 숫자를 보고할 때는 '콘텐츠에서 온 것'과 '화면에서 온 것'을 구분해야 하며, 후자는 반드시 템플릿의 md5를 첨부해야 합니다. 콘텐츠에서 온 것(후크, 염색, 밝은 블록, 자막 블록, 무음, 말하기 속도)은
words.json에 바인딩되며, 해당 md5가 변경되지 않으면 숫자는 단 한 비트도 변경되지 않습니다; 화면에서 온 것(프레임 차이, 글자 잘림)은 녹화 당시의 템플릿 버전에 대해서만 유효합니다. 둘을 섞어서 보고하면, 다음 사람이 문서에 따라 재녹화할 때 일치하지 않는 숫자를 얻게 됩니다: 실제로 동일한 클립에서, 템플릿 변경 전에 녹화된 초당 변화량은 14.80이었고, 변경 후에는 27.19였습니다——1.8배 차이가 나며, 콘텐츠의 여섯 항목은 하나도 다르지 않았습니다. 따라서 상대방은 존재하지 않는 회귀를 찾기 위해 한 번의 반복을 소비했습니다.capture-short.mjs는 이제 매 녹화 시마다템플릿 short-9x16.html md5 …를 출력하고, 숫자를 보고할 때 함께 첨부합니다. -
녹화 자체에 떨림이 있으므로, 판단 기준은 이 떨림을 견딜 수 있어야 합니다. 템플릿과 입력을 md5로 고정하고, 연속 네 번 녹화하면, 슬라이드 밴드의 초당 변화량은 21.27 / 21.33 / 20.82 / 21.3입니다.
…