[이 모듈이 하는 일] 설계도에서 스톡 영상(media.type "stock-video") 장면의 무료 스톡 영상을 찾아 확보한다. 검색 → 영어 관련도 순위 → 규격 거르기(길이·해상도·방향) → 가져오기 → 프레임을 직접 보는 화면 검수 순서로, 장면이 말하는 일반 장면과 맞고 글자·로고·엉뚱한 나라·시대가 없는 영상만 남긴다. 스톡 소스는 회원이 API 키를 넣은 곳만 쓴다(Pexels 키 → Pexels, Pixabay 키 → Pixabay). 키가 하나도 없으면 스톡을 찾지 않고 모든 스톡 장면을 AI 이미지로 넘긴다. 확보한 영상은 장면 길이에 맞춰 쓸 구간을 잘라 assets["media:<장면id>"] 에 적고, 못 찾은 장면은 AI 이미지로 대체되도록 표시한다. 모든 후보의 채택·탈락 이유를 스톡 원장(stockLedger)에 남긴다. [필요한 값] - 옵션 | secret | pexels_api_key | Pexels API 키 | provider=pexels help="pexels.com 에서 무료로 발급받은 키를 넣으면 Pexels 에서 스톡 영상을 찾습니다. 넣지 않으면 Pexels 는 쓰지 않습니다." - 옵션 | secret | pixabay_api_key | Pixabay API 키 | provider=pixabay help="pixabay.com 에서 무료로 발급받은 키를 넣으면 Pixabay 에서 스톡 영상을 찾습니다. 넣지 않으면 Pixabay 는 쓰지 않습니다." [입력] 이전 단계 JSON(vidia.doc@1). - blueprint.video.aspectRatio: 화면 방향(16:9 가로 / 9:16 세로 / 1:1) - blueprint.meta: {title, category} — 영상의 시대·나라 맥락 판단용 - blueprint.scenes[]: {id, title, purpose, utterances[{ttsText}], x-search{query, alt}(검색 단서, 있을 때), media{type, stock{provider, id, url, startSec}}, keyframes[{imagePrompt}]} - research.mediaLeads.stockQueries: 소주제별 스톡 검색어(있을 때) - timeline.scenes: {"<장면id>": {durationSec}} (음성 단계 결과. 없으면 발화 글자 수 ÷ 7 + 1초) - input.stock_visual_check: false 면 프레임 검수를 건너뛴다(기본 true) - assets: 이미 확보한 "media:<장면>" 은 다시 찾지 않는다. [할 일] 0. 쓸 수 있는 소스를 정한다(도구를 부르기 전에). - [설정]의 "사용할 수 있는 비밀값" 목록에 pexels_api_key 가 있으면 "pexels", pixabay_api_key 가 있으면 "pixabay" 를 쓸 수 있다. 목록에 없는 소스는 이번 실행에서 절대 부르지 않는다(서비스 키로 대신하지 않는다). - 쓸 수 있는 소스가 하나도 없으면 도구를 부르지 않는다. 대상 장면을 모두 mediaMissing 에 {"scene": 장면id, "type": "stock-video", "reason": "스톡 API 키 없음(회원 키를 넣지 않아 스톡을 쓰지 않음)"} 으로 적고 stockStats.sources 를 [] 로 두고 끝낸다. - stock_search·stock_fetch 는 항상 own_key_only: true 로 부른다. STOCK_OWN_KEY_NOT_SET 오류가 오면 그 소스를 쓸 수 없는 목록으로 옮기고 다시 부르지 않는다. 대상 장면: media.type 이 "stock-video" 이고 assets["media:<장면id>"] 가 없는 장면. 하나도 없으면 도구를 부르지 않고 stockStats 만 더해 내보낸다. 장면 길이 D = timeline.scenes[장면id].durationSec(없으면 추정). 필요한 원본 길이 = D + 2초. 1. 이미 정해진 원본: scene.media.stock.provider 가 쓸 수 있는 소스이고 id 가 있으면 검색 없이 4번(가져오기)부터 한다. 2. 검색어(장면마다) - 첫 검색어: scene["x-search"].query, 없으면 research.mediaLeads.stockQueries 에서 같은 소주제·대상의 query, 그것도 없으면 키프레임 imagePrompt 첫 문장과 purpose 에서 눈에 보이는 대상·장소·동작만 뽑은 영어 3~7 단어(예: "container ship cranes harbour aerial", "night highway traffic timelapse"). - 고유명사·연도·감정어·추상어("crisis", "success")는 넣지 않는다. 얼굴·로고·글자·문서·화면(모니터 글자)이 주인공인 대상은 스톡으로 찾지 않는다 — 그런 장면이면 검색하지 말고 mediaMissing(사유 "스톡에 맞지 않는 대상")으로 AI 이미지에 넘긴다. - 대체 검색어: x-search.alt 또는 stockQueries 의 alt, 없으면 한 단계 일반적인 말("cargo ship sea" → "ocean ship"). 3. 검색과 순위 - stock_search(query, provider, orientation, per_page 12, own_key_only true). provider 는 쓸 수 있는 소스 가운데 scene.media.stock.provider 가 있으면 그것, 아니면 "pexels"(자연·풍경·도시 전경은 "pixabay" 가 먼저여도 좋다), 쓸 수 있는 소스가 하나면 그것. orientation 은 16:9 "landscape", 9:16 "portrait", 1:1 은 넣지 않는다. - 결과가 비었거나 아래 거르기 뒤 후보가 없으면 대체 검색어로, 그래도 없으면 쓸 수 있는 다른 provider 가 있을 때만 그것으로 찾는다. 장면당 검색 최대 3번. - 회원 키가 거부되거나(STOCK_KEY_INVALID) 한도에 걸리면(STOCK_RATE_LIMITED) 제작이 멈추고 회원에게 키를 고치도록 안내된다. 이 모듈이 다른 키로 우회하지 않는다. - 규격 거르기: duration_sec ≥ D + 2, 방향이 맞고(가로 영상은 width > height), 가로 영상은 1280×720 이상(세로는 720×1280 이상), 이 실행에서 이미 쓴 provider:id 가 아닌 것. - 관련도 순위(0~100): attribution·preview_url 에 보이는 제목·태그 낱말이 검색어의 대상·장소·동작과 얼마나 겹치는지. 10 미만은 뺀다. 1920×1080 이상, 길이가 넉넉한 것을 같은 점수에서 앞에 둔다. 상위 3개만 남긴다. 4. 가져오기와 화면 검수(장면당 최대 3개 후보, 앞에서부터) - stock_fetch(provider, id, own_key_only true) 로 가져온다(무료). 결과 asset_id·duration_sec·width·height·license·attribution 을 기록한다. 결과 길이·해상도가 규격에 못 미치면 탈락. - input.stock_visual_check 가 false 가 아니면 analyze_visual(asset_id, focus "reference_video", frames 6, question) 로 실제 프레임을 본다. question(한국어): "이 스톡 영상을 '<장면 purpose 한 줄>' 장면의 일반 화면으로 쓰려 한다. 1) 화면에 <찾는 대상·동작>이 보이는가 2) 읽을 수 있는 글자·간판·상표·로고·자막·날짜가 있는가(있으면 그대로 적기) 3) 나라·시대가 <영상 맥락: 예 1990년대 한국 / 현대 일반>과 어긋나는 단서(다른 나라 차량·간판·복장, 현대 장비)가 있는가 4) 얼굴이 크게 잡힌 사람이 있는가 5) 가장 쓸 만한 구간은 몇 번째 프레임 부근인가." - 채택 기준: 대상·동작이 보이고, 읽을 수 있는 글자·로고가 없고, 나라·시대가 어긋나지 않고, 얼굴 클로즈업이 주인공이 아니다. 하나라도 어긋나면 탈락 사유를 적고 다음 후보를 본다. 3개를 봐도 없으면 mediaMissing(사유 "맞는 스톡 영상 없음")으로 둔다. - 스톡은 일반 장면용이다. 장면이 특정 사건의 실제 화면을 요구하는 것으로 보이면(발화가 "이 영상은 당시…"처럼 실제 기록을 가리킴) 채택하지 말고 mediaMissing(사유 "실제 사건 화면이 필요한 장면")으로 둔다. 5. 쓸 구간 자르기(무료) - 시작 위치 startSec: 검수가 가장 쓸 만하다고 한 프레임 위치(frame 번호 ÷ 프레임 수 × 원본 길이)에서 D 만큼이 원본 안에 들어가게 정한다(startSec + D ≤ 원본 길이 − 0.5). 검수를 건너뛰었으면 원본 길이가 D 의 2배 이상일 때 1초, 아니면 0초. - make_clip(asset_id=원본, duration_sec=D, start_sec=startSec, label="media:<장면id>") 로 장면 길이에 맞춘 클립을 만든다. timeline 이 없어 D 가 추정값이면 자르지 않고 원본 asset_id 를 쓴다. - 결과 asset_id 를 assets["media:<장면id>"] 에 적는다. 6. 기록 - credits 에 {"scene": 장면id, "source": provider, "title": attribution, "author": attribution 의 제작자, "url": page_url(있으면), "license": license} 를 더한다. - stockLedger 에 장면마다 {"scene": 장면id, "queries": ["…"], "chosen": {"provider": "…", "id": "…", "assetId": 원본 asset_id, "clipAssetId": 자른 클립 asset_id, "startSec": …, "durationSec": 원본 길이, "width": …, "height": …, "relevance": …, "visual": "검수 요약 한 줄"} 또는 null, "rejected": [{"provider": "…", "id": "…", "reason": "…"}](최대 5개)}. [출력] 다음 최상위 필드만 담은 JSON 객체 하나(적지 않은 최상위 필드는 입력값이 그대로 이어진다. 바꾸는 필드는 값 전체를 적는다). - assets: "media:<장면id>" 키를 더한다(기존 키는 모두 그대로). - mediaMissing: 기존 mediaMissing 가운데 이번에 확보한 장면을 뺀 것 + 이번에 못 찾은 stock-video 장면 - credits: 기존 credits 뒤에 이번 기록을 더한 배열 - stockLedger: 이번 실행의 스톡 원장 - stockStats: {"sources": 이번에 쓸 수 있었던 소스 목록(예 ["pexels"]), "target": 대상 장면 수, "found": 확보 수, "missing": 못 찾은 수, "searches": stock_search 호출 수, "checked": analyze_visual 호출 수} [주의] - stock_search·stock_fetch·make_clip 은 무료지만 analyze_visual 은 판정마다 비용이 든다(보통 1~5P). 장면당 최대 3번, 같은 영상을 두 번 검수하지 않는다. - 스톡 API 키는 회원이 넣은 키만 쓴다. 키 원문은 도구가 알아서 쓰며 지시서나 출력에 키를 적지 않는다. - 같은 스톡 영상(provider:id)을 두 장면에 쓰지 않는다. 스톡 영상을 실제 사건 기록처럼 설명하지 않는다. - 설계도(blueprint)와 시간표(timeline)는 바꾸지 않는다. 못 찾은 장면은 다음 이미지 단계가 키프레임으로 AI 이미지를 만든다. - 설명·마크다운 없이 JSON 객체 하나만 답한다.