aimakeall-mcp 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ Claude Code·Codex 같은 MCP 클라이언트에서 자연어로 AImakeAll 영
16
16
  "mcpServers": {
17
17
  "aimakeall": {
18
18
  "command": "npx",
19
- "args": ["-y", "aimakeall-mcp@0.9.1"],
19
+ "args": ["-y", "aimakeall-mcp@0.11.0"],
20
20
  "env": { "AIMAKEALL_PAT": "aio_pat_..." }
21
21
  }
22
22
  }
@@ -35,21 +35,37 @@ Claude Code·Codex 같은 MCP 클라이언트에서 자연어로 AImakeAll 영
35
35
  ## 기본 플로우 (쇼츠)
36
36
 
37
37
  ```
38
- plan_shorts_video → (씬마다) generate_scene_image → [QC: 동봉 이미지 직접 확인 또는 verify_scene_image] → generate_scene_video_prompt → generate_scene_video → [QC: 동봉 프레임 확인 또는 verify_scene_video]
39
- → (보이스 미지정 시) recommend_voice → tts_narration_with_captions(하단자막·강조용) 또는 tts_narration → stitch_timeline → render_start → render_status(폴링) → render_result
38
+ plan_shorts_video → (씬마다) generate_scene_image → get_generation_job(completed까지 조회) → [QC: 동봉 이미지 직접 확인 또는 verify_scene_image] → generate_scene_video_prompt → generate_scene_video → get_generation_job(completed까지 조회) → [QC: 동봉 프레임 확인 또는 verify_scene_video]
39
+ → (보이스 미지정 시) recommend_voice → tts_narration_with_captions(하단자막·강조용) 또는 tts_narration
40
+ → list_composer_presets + search_composer_assets / recommend_composer_overlays
41
+ → stitch_timeline → review_production → render_start → render_status(폴링) → render_result → 최종 프레임·음성 검토
40
42
  → ai_publish_metadata → publish_youtube
41
43
  ```
42
44
 
43
- - 씬 영상 생성은 동기 호출로 최대 15분 걸립니다 — MCP 클라이언트의 툴 타임아웃(`MCP_TIMEOUT`)을 충분히 잡아주세요.
44
- - 렌더 잡은 `~/.aimakeall/render-jobs.json`에 기록되어 프록시 재시작 후 `list_render_jobs`로 복구할 수 있습니다.
45
+ - 이미지·영상 생성은 즉시 `generationJobId`를 반환합니다. `get_generation_job`으로 상태를 조회하고 `completed`의 `imageUrl`/`videoUrl`을 다음 단계에 사용하세요. 연결이 끊겼을 때는 재생성 전에 `list_generation_jobs`로 기존 작업을 확인하세요. 완료 결과는 서버 DB에 48시간 보관되며 계정 소유자만 조회할 수 있습니다.
46
+ - 서버 자체가 생성 중 재시작되면 작업은 `interrupted`로 표시됩니다. 이미 접수된 `providerTasks`와 비용 원장을 확인해야 하며, 자동으로 유료 생성을 재실행하지 않습니다. 이 기능에는 0.10.0 대응 서버가 필요합니다.
47
+ - 렌더 잡은 `~/.aimakeall/render-jobs/` 아래 작업별 파일에 기록되어 프록시 재시작 후 `list_render_jobs`로 복구할 수 있습니다. 기존 `render-jobs.json`도 계속 읽을 수 있습니다.
45
48
  - 대용량 산출물(TTS mp3, 스티치 매니페스트)은 파일 경로로 주고받아 모델 컨텍스트를 오염시키지 않습니다.
46
49
  - `recommend_voice`는 주제·샘플 영상(유튜브)에 맞는 보이스를 자동 선정합니다 — 샘플 화자 분석은 컴패니언 앱이 실행 중일 때만 동작합니다.
47
50
  - 자체검증(QC): 씬 이미지/영상 결과에 이미지·프레임 블록이 동봉되어 에이전트가 직접 보고 판정합니다. 비전 미지원 클라이언트는 `verify_scene_image`/`verify_scene_video`(서버 Gemini 판정)를 쓰세요 — 불합격 시 `regenerationHint`를 반영해 재생성.
48
- - 하단자막 강조: `tts_narration_with_captions`의 `subtitleLines`와 `plan_shorts_video`의 `emphasisKeywords`를 `stitch_timeline`에 함께 넘기면 자막 안 해당 단어만 노랑/큰 글씨로 강조됩니다(최신 컴패니언 런타임 필요 — 구버전은 일반 자막으로 안전 강등).
51
+ - 하단자막 강조: `subtitleLines`와 `emphasisKeywords`를 함께 넘기거나 제작 핸들을 사용하면 해당 단어만 색/크기로 강조됩니다. 새 타임라인은 필요한 렌더 기능을 명시하며, 구형 런타임에서는 조용히 무시하지 않고 업데이트를 요청합니다.
49
52
  - 렌더 영수증: `render_status`(완료 시)와 `render_result`가 산출물 실측(`summary.measured` — 길이·해상도·오디오·디코드 청결성·파일 크기)을 요청 규격과 대조해 `mismatches`로 알려줍니다. 불일치가 있으면 저장 전 원인을 확인하세요.
50
53
  - 원가 가시화: 생성 전 `estimate_video_cost`로 견적을 내고, 작업 후 `get_cost_report`로 실제 지출을 확인하세요. 어두운/검은 컷 의심 시 `verify_render_darkness`로 완성본 휘도를 실측할 수 있습니다.
51
54
  - 캐릭터 일관성: `plan_*`에 `characters`(이름·외모 앵커·의상 고정)를 넘기면 씬마다 identity lock 이 프롬프트에 강제 주입되고, `generate_scene_image`의 `identityLock`으로 재생성 시에도 유지됩니다.
52
55
 
56
+ ## 제작 스타일 유지와 컷컴포저 편집 (0.11.0)
57
+
58
+ 이 절의 도구는 MCP 0.11.0과 대응 서버·컴패니언 런타임이 필요합니다. 기존 MCP 설정의 버전을 `aimakeall-mcp@0.11.0`으로 바꾸고 클라이언트를 다시 연결하세요. 컴패니언에서 렌더 기능 업데이트를 요청하면 최신 설치 프로그램으로 업데이트하세요.
59
+
60
+ - `plan_shorts_video({categoryId:"viral-shorts",durationTargetSec:40,...})`는 `productionProfile`과 `productionPath`를 반환합니다. 이후 추천·TTS·스티치에 **가장 최근 반환된 `productionPath`**를 전달하세요. 제작별 불변 파일이라 동시에 만든 다른 영상의 스타일과 섞이지 않습니다.
61
+ - 바이럴·커뮤니티 프로필은 `casual-social`을 사용합니다. 사건 소재라도 뉴스 브리핑형으로 자동 변경하지 않습니다. TTS의 `text`를 생략하면 저장된 기획 대본을 사용하며, 분명한 뉴스체 회귀는 과금 전 `422 NARRATION_STYLE_DRIFT`로 중단합니다. 자막은 실제 발화와 일치시켜야 하며 자극성을 위해 사실·인용·피해 내용을 꾸미면 안 됩니다.
62
+ - 프로필이 있으면 보이스 추천 기본 공급자는 ElevenLabs입니다. 카탈로그에서 대화형/소셜 특성을 우선하되 실제 청취 결과라고 주장하지 않습니다. `speed`, `stability`, `style`, `similarityBoost`, `speakerBoost`, `modelId`는 명시적으로 조절할 수 있습니다.
63
+ - `list_composer_presets`에서 실제 편집기의 프리셋과 모션을 조회하세요. `titleStylePresetId`, `subtitleStylePresetId`, `subtitleLines[].stylePresetId`, `titleStyle`, `subtitleStyle`, `subtitleLines[].style`로 서로 다른 제목/본문 계층과 장면별 강조를 설정합니다. 명시 스타일 > 선택 프리셋 > 프로필 기본값 순서입니다.
64
+ - `search_composer_assets` 또는 무료 `recommend_composer_overlays`로 관련 소재를 검토하고 선택한 `assetId`에 `startSec/endSec`, `xPct/yPct`, `widthPct`를 붙여 `stitch_timeline.overlays`로 전달하세요. 좌표는 화면의 백분율 중심점이며 `opacity`는 0~100입니다. SVG 아이콘·이모지, GIF/비디오, 효과음을 타임라인 레이어로 처리합니다. 사건·피해자에 조롱 밈을 강제로 붙이지 않으며, 출처·라이선스 확인이 필요한 소재는 공개 전에 검토해야 합니다.
65
+ - 목표 길이는 실제 TTS 길이로 덮어쓰지 않습니다. 허용 오차 `max(2초, 목표의 10%)`를 벗어나면 경고하고 렌더 전에 차단합니다. 자동 유료 재합성을 하지 않으며, 무음이나 억지 영상 늘이기로 통과시키지 마세요.
66
+ - 커뮤니티/바이럴 제작은 라이브러리 검토 없이 기본 자막만으로 끝내지 않습니다. 오버레이가 없으면 `decorationRationale`에 적합한 소재를 생략한 이유를 기록해야 렌더할 수 있습니다. 관련 없는 장식이나 부적절한 밈을 억지로 넣는 것은 해결이 아닙니다.
67
+ - `review_production`은 구조만 점검합니다. `render_result`의 `qualityStatus`가 `needs-visual-and-listening-review`이면 아직 시각·청취 검증 전입니다. 최종 영상에서 강조색/글자 크기/모션/소재 겹침과 목소리를 직접 확인한 뒤에만 완성 품질을 보고하세요.
68
+
53
69
  ## 채널 규격 지문 (벤치마킹)
54
70
 
55
71
  ```
@@ -0,0 +1,57 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { mp3DurationSec } from "./mp3-duration.mjs";
5
+
6
+ // WAV duration comes from sample frames, never an assumed MP3 bitrate. Walk RIFF
7
+ // chunks because encoders may include JUNK/LIST metadata before the audio data.
8
+ export function wavDurationSec(buffer) {
9
+ if (!Buffer.isBuffer(buffer) || buffer.length < 44
10
+ || buffer.toString("ascii", 0, 4) !== "RIFF"
11
+ || buffer.toString("ascii", 8, 12) !== "WAVE") return 0;
12
+ let format = null;
13
+ let dataBytes = 0;
14
+ for (let offset = 12; offset + 8 <= buffer.length;) {
15
+ const id = buffer.toString("ascii", offset, offset + 4);
16
+ const size = buffer.readUInt32LE(offset + 4);
17
+ const body = offset + 8;
18
+ if (id === "fmt " && size >= 16 && body + size <= buffer.length) {
19
+ let encoding = buffer.readUInt16LE(body);
20
+ if (encoding === 0xfffe && size >= 40) {
21
+ // WAVE_FORMAT_EXTENSIBLE PCM / IEEE_FLOAT subformat GUID.
22
+ const guidTail = buffer.subarray(body + 28, body + 40).toString("hex");
23
+ encoding = guidTail === "00001000800000aa00389b71" ? buffer.readUInt32LE(body + 24) : 0;
24
+ }
25
+ format = {
26
+ encoding,
27
+ channels: buffer.readUInt16LE(body + 2),
28
+ sampleRate: buffer.readUInt32LE(body + 4),
29
+ blockAlign: buffer.readUInt16LE(body + 12),
30
+ bits: buffer.readUInt16LE(body + 14),
31
+ };
32
+ } else if (id === "data") {
33
+ dataBytes += Math.min(size, buffer.length - body);
34
+ }
35
+ offset = body + size + (size % 2);
36
+ }
37
+ if (!format || !format.channels || !format.sampleRate) return 0;
38
+ const allowedBits = format.encoding === 1 ? [8, 16, 24, 32] : format.encoding === 3 ? [32, 64] : [];
39
+ if (!allowedBits.includes(format.bits)) return 0;
40
+ const blockAlign = format.channels * (format.bits / 8);
41
+ if (format.blockAlign !== blockAlign) return 0;
42
+ return Math.round((Math.floor(dataBytes / blockAlign) / format.sampleRate) * 100) / 100;
43
+ }
44
+
45
+ export function audioDurationSecFromFile(filePath) {
46
+ const buffer = readFileSync(filePath);
47
+ if (path.extname(filePath).toLowerCase() === ".wav"
48
+ || (buffer.toString("ascii", 0, 4) === "RIFF" && buffer.toString("ascii", 8, 12) === "WAVE")) {
49
+ const seconds = wavDurationSec(buffer);
50
+ if (seconds > 0) return seconds;
51
+ throw new Error("WAV 길이를 읽지 못했습니다. PCM WAV로 변환하거나 audioDurationSec에 실제 길이를 지정하세요.");
52
+ }
53
+ const seconds = mp3DurationSec(buffer);
54
+ if (seconds > 0) return seconds;
55
+ // Preserve the existing approximation only for unparseable MP3 inputs.
56
+ return Math.round((buffer.length * 8) / (128 * 1000) * 10) / 10;
57
+ }