@leejungkiin/awkit 1.7.1 → 1.7.4
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/bin/awk.js +576 -84
- package/core/CLAUDE.md +1 -1
- package/core/GEMINI.md +148 -167
- package/core/GEMINI.md.bak +149 -116
- package/core/skill-runtime-manifest.json +3 -0
- package/docs/Claude Fable 5.md +3826 -0
- package/docs/android_kotlin_system_instruction.md +210 -0
- package/docs/brainstorm_ponytail_integration.md +146 -0
- package/docs/brainstorm_smart_setup.md +113 -0
- package/docs/deep-research-report (1).md +293 -0
- package/docs/history/GEMINI.v1.md +135 -0
- package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
- package/docs/history/implementation_plan.v1.md +58 -0
- package/package.json +4 -1
- package/scripts/artifact-storage.js +130 -0
- package/scripts/automation-gate.js +35 -2
- package/scripts/claude-plan.js +76 -0
- package/scripts/dependency-manager.js +210 -0
- package/scripts/exec-rtk.js +11 -5
- package/scripts/i18n-helper.js +381 -0
- package/scripts/multi-model-pipeline.js +144 -0
- package/skill-packs/mobile-ios/pack.json +4 -2
- package/skill-packs/reverse-engineering/pack.json +1 -0
- package/skills/CATALOG.md +20 -0
- package/skills/GEMINI.md +9 -1
- package/skills/TRIGGER_INDEX.md +10 -0
- package/skills/ai-music/SKILL.md +275 -0
- package/skills/android-re-analyzer/SKILL.md +238 -0
- package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
- package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
- package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
- package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
- package/skills/android-re-analyzer/references/setup-guide.md +221 -0
- package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
- package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
- package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
- package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
- package/skills/animal-island-ui-style/SKILL.md +1450 -0
- package/skills/app-store-review-agent/SKILL.md +164 -0
- package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
- package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
- package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
- package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
- package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
- package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
- package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
- package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
- package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
- package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
- package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
- package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
- package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
- package/skills/awf-ponytail/SKILL.md +91 -0
- package/skills/awf-ponytail-review/SKILL.md +67 -0
- package/skills/awf-session-restore/SKILL.md +3 -3
- package/skills/brainstorm-agent/SKILL.md +11 -2
- package/skills/brainstorm-agent/templates/brief-template.md +8 -0
- package/skills/claude-planner/SKILL.md +47 -0
- package/skills/code-review/SKILL.md +87 -0
- package/skills/expo-game-development/SKILL.md +163 -0
- package/skills/flutter/LICENSE.txt +202 -0
- package/skills/flutter/SKILL.md +127 -0
- package/skills/flutter-project-creater/LICENSE.txt +202 -0
- package/skills/flutter-project-creater/SKILL.md +106 -0
- package/skills/game-developer/SKILL.md +163 -0
- package/skills/game-developer/references/ecs-patterns.md +501 -0
- package/skills/game-developer/references/multiplayer-networking.md +475 -0
- package/skills/game-developer/references/performance-optimization.md +422 -0
- package/skills/game-developer/references/unity-patterns.md +271 -0
- package/skills/game-developer/references/unreal-cpp.md +352 -0
- package/skills/generate-gui-assets/SKILL.md +305 -0
- package/skills/generate-gui-assets/agents/openai.yaml +4 -0
- package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
- package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
- package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
- package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
- package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
- package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
- package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
- package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
- package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
- package/skills/godot-game-development/SKILL.md +142 -0
- package/skills/hatch-pet/LICENSE.txt +201 -0
- package/skills/hatch-pet/SKILL.md +420 -0
- package/skills/hatch-pet/agents/openai.yaml +4 -0
- package/skills/hatch-pet/references/animation-rows.md +29 -0
- package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
- package/skills/hatch-pet/references/qa-rubric.md +60 -0
- package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
- package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
- package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
- package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
- package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
- package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
- package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
- package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
- package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
- package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
- package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
- package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
- package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
- package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
- package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
- package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
- package/skills/i18n-orchestrator/SKILL.md +37 -0
- package/skills/ios-simulator-skill/SKILL.md +390 -0
- package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
- package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
- package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
- package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
- package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
- package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
- package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
- package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
- package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
- package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
- package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
- package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
- package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
- package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
- package/skills/ios-simulator-skill/scripts/container.py +668 -0
- package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
- package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
- package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
- package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
- package/skills/ios-simulator-skill/scripts/location.py +467 -0
- package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
- package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
- package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
- package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
- package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
- package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
- package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
- package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
- package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
- package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
- package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
- package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
- package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
- package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
- package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
- package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
- package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
- package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
- package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
- package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
- package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
- package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
- package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
- package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
- package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
- package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
- package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
- package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
- package/skills/orchestrator/SKILL.md +0 -20
- package/skills/persistent-storage/SKILL.md +55 -0
- package/skills/short-maker/SKILL.md +23 -0
- package/skills/short-maker/scripts/effects.js +56 -0
- package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
- package/skills/short-maker/scripts/videomix.js +601 -0
- package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
- package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
- package/skills/smali-to-kotlin/SKILL.md +128 -0
- package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
- package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
- package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
- package/skills/smali-to-kotlin/library-patterns.md +189 -0
- package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
- package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
- package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
- package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
- package/skills/smali-to-kotlin/phase-3-build.md +248 -0
- package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
- package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
- package/skills/smali-to-kotlin/templates/app-map.md +101 -0
- package/skills/smali-to-kotlin/templates/architecture.md +142 -0
- package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
- package/skills/spec-gate/SKILL.md +6 -2
- package/skills/symphony-enforcer/SKILL.md +8 -0
- package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
- package/skills/symphony-enforcer/examples/three-phase.md +16 -0
- package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
- package/skills/unity-game-development/SKILL.md +231 -0
- package/skills/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- package/templates/project-identity/android.json +2 -2
- package/templates/project-identity/backend-nestjs.json +2 -2
- package/templates/project-identity/expo.json +2 -2
- package/templates/project-identity/ios.json +2 -2
- package/templates/project-identity/web-nextjs.json +2 -2
- package/templates/setup-mapping.json +48 -0
- package/templates/specs/design-template.md +161 -71
- package/templates/specs/requirements-template.md +65 -133
- package/templates/specs/task-spec-template.xml +3 -0
- package/workflows/_uncategorized/critic.md +40 -0
- package/workflows/_uncategorized/git-rebase-flow.md +81 -0
- package/workflows/_uncategorized/image-gen.md +118 -0
- package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
- package/workflows/_uncategorized/pixel-gen.md +86 -0
- package/workflows/_uncategorized/pixel-setup.md +90 -0
- package/workflows/_uncategorized/ponytail-review.md +59 -0
- package/workflows/_uncategorized/reverse-android-build.md +222 -0
- package/workflows/_uncategorized/reverse-android-design.md +139 -0
- package/workflows/_uncategorized/reverse-android-discover.md +150 -0
- package/workflows/_uncategorized/reverse-android-scan.md +158 -0
- package/workflows/_uncategorized/reverse-android.md +143 -0
- package/workflows/_uncategorized/reverse-ios-build.md +240 -0
- package/workflows/_uncategorized/reverse-ios-design.md +112 -0
- package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
- package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
- package/workflows/_uncategorized/reverse-ios.md +152 -0
- package/workflows/_uncategorized/safety-router.md +34 -0
- package/workflows/_uncategorized/teach.md +89 -0
- package/workflows/_uncategorized/verify-ui.md +53 -0
- package/workflows/_uncategorized/visualize-screenshots.md +34 -0
- package/workflows/ads/ads-analyst.md +201 -0
- package/workflows/ads/ads-audit.md +106 -0
- package/workflows/ads/ads-optimize.md +97 -0
- package/workflows/ads/ads-targeting.md +241 -0
- package/workflows/ads/adsExpert.md +160 -0
- package/workflows/ads/smali-ads-config.md +400 -0
- package/workflows/ads/smali-ads-flow.md +331 -0
- package/workflows/ads/smali-ads-interstitial.md +377 -0
- package/workflows/ads/smali-ads-native.md +382 -0
- package/workflows/context/teach.md +89 -0
- package/workflows/gitnexus.md +8 -8
- package/workflows/lifecycle/brainstorm.md +43 -0
- package/workflows/lifecycle/code.md +5 -0
- package/workflows/lifecycle/init.md +23 -5
- package/workflows/lifecycle/multi-model-pipeline.md +60 -0
- package/workflows/quality/ponytail-review.md +59 -0
- package/workflows/roles/critic.md +40 -0
- package/workflows/roles/safety-router.md +34 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Visual Reasoning Heuristics
|
|
2
|
+
|
|
3
|
+
Inspect screenshots like a focused QA pass, not like a design critique.
|
|
4
|
+
|
|
5
|
+
## First Pass
|
|
6
|
+
|
|
7
|
+
Identify:
|
|
8
|
+
|
|
9
|
+
- current screen name or purpose
|
|
10
|
+
- visible user state: logged out, logged in, onboarding, loading, empty, error, populated, editing, confirming
|
|
11
|
+
- primary and secondary actions
|
|
12
|
+
- whether the screen matches the expected step in the test plan
|
|
13
|
+
|
|
14
|
+
## Layout Checks
|
|
15
|
+
|
|
16
|
+
Look for:
|
|
17
|
+
|
|
18
|
+
- clipped text, truncated labels, ellipses on critical content
|
|
19
|
+
- overlapping views, stacked buttons, hidden controls
|
|
20
|
+
- safe-area issues at top, bottom, keyboard, Dynamic Island, notch, and home indicator
|
|
21
|
+
- scrollability when content exceeds viewport
|
|
22
|
+
- tap targets that appear too small or crowded
|
|
23
|
+
- modal/sheet height problems
|
|
24
|
+
- keyboard covering input or submit buttons
|
|
25
|
+
|
|
26
|
+
## Content Checks
|
|
27
|
+
|
|
28
|
+
Look for:
|
|
29
|
+
|
|
30
|
+
- placeholder content leaking into production flows
|
|
31
|
+
- stale loading indicators
|
|
32
|
+
- empty state copy that does not match the action available
|
|
33
|
+
- inconsistent numbers, dates, currency, units, or language
|
|
34
|
+
- missing required data after save/import/sync
|
|
35
|
+
- destructive actions without clear confirmation
|
|
36
|
+
|
|
37
|
+
## State Inference
|
|
38
|
+
|
|
39
|
+
Use caution when inferring:
|
|
40
|
+
|
|
41
|
+
- success from a screen transition alone
|
|
42
|
+
- network completion from a spinner disappearing
|
|
43
|
+
- persistence from visible text before app relaunch
|
|
44
|
+
- analytics/tracking from UI state
|
|
45
|
+
- permission correctness from a prompt appearing
|
|
46
|
+
|
|
47
|
+
When inference is not enough, pair the screenshot with logs, accessibility assertions, app relaunch, or a deterministic test.
|
|
48
|
+
|
|
49
|
+
## Confidence Labels
|
|
50
|
+
|
|
51
|
+
- High: the screenshot directly shows the claim or a deterministic assertion confirms it.
|
|
52
|
+
- Medium: the visible state plus accessibility/log evidence supports the claim.
|
|
53
|
+
- Low: the claim depends on code/spec inference without direct observation.
|
|
@@ -68,25 +68,5 @@ No match → Ask clarifying question (max 2 times)
|
|
|
68
68
|
Still unclear → Suggest `/help`
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
### 5. Model Fitness Check (Pre-Execution Gate)
|
|
72
|
-
```yaml
|
|
73
|
-
model_fitness_gate:
|
|
74
|
-
purpose: "Đúng mô hình — đúng việc — tránh lãng phí token và tránh hallucination do mismatch."
|
|
75
|
-
action: "Orchestrator must analyze task complexity vs current model tier before code execution."
|
|
76
|
-
tiers:
|
|
77
|
-
- LIGHT: Flash/Haiku/4o-mini (config, syntax, docs, simple boilerplate)
|
|
78
|
-
- STANDARD: Pro/Sonnet/4o (90% dev work, active coding, feature additions, standard bug fixes)
|
|
79
|
-
- HEAVY: Opus/Ultra/o1/o3 (Architecture, custom framework, refactor > 5 files, deep debugging)
|
|
80
|
-
detect_signals:
|
|
81
|
-
escalate_if: "Blast radius > 5 files, ≥ 2 custom types (e.g. protocol matching), COMPLEX triage, circular self-correction"
|
|
82
|
-
downgrade_if: "Single file < 20 lines, *.plist|*.json|*.env, string replacements, no logic changes"
|
|
83
|
-
procedure:
|
|
84
|
-
1: "Phân loại mức độ phức tạp của Task để chốt Hạng Yêu Cầu (Required Tier) (tham khảo project-identity modelPolicy hoặc tự nội suy)."
|
|
85
|
-
2: "Chú ý Môi trường IDE (Cursor, Gemini) -> AI KHÔNG THỂ tự động đổi Model."
|
|
86
|
-
3: "Nếu phát hiện Mismatch (Over-fit lãng phí token như dùng Opus cho task dễ, HOẶC Under-fit sinh lỗi như dùng Flash cho task khó) -> Print ⚠️ MODEL TRIAGE CHECKPOINT và BẮT BUỘC DỪNG quá trình."
|
|
87
|
-
4: "Yêu cầu user tự chuyển đổi model trên IDE hoặc gõ 'Tiếp tục' để ép buộc chạy model hiện tại."
|
|
88
|
-
5: "Nếu Match (Đạt yêu cầu) -> Tiếp tục chạy bình thường."
|
|
89
|
-
```
|
|
90
|
-
|
|
91
71
|
## Auto-Activation
|
|
92
72
|
This skill is always active. It runs as the first layer before any other processing.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# SKILL: persistent-storage
|
|
2
|
+
|
|
3
|
+
Cung cấp API và cơ chế lưu trữ dữ liệu key-value bền vững (persistent key-value storage) cho các tài liệu tương tác (Artifacts) trong Antigravity.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🛠️ API Định nghĩa (Storage API)
|
|
8
|
+
|
|
9
|
+
Các Artifacts tương tác (HTML/JS chạy trên browser) có thể tương tác với storage thông qua các hàm bất đồng bộ. Đối với AI Agent, bạn có thể gọi trực tiếp thông qua công cụ dòng lệnh (CLI):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
awkit storage set <key> '<json_value>' [--shared]
|
|
13
|
+
awkit storage get <key> [--shared]
|
|
14
|
+
awkit storage delete <key> [--shared]
|
|
15
|
+
awkit storage list [prefix] [--shared]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 1. `storage_get(key, shared?)`
|
|
19
|
+
- **Mục tiêu:** Lấy giá trị tương ứng với key.
|
|
20
|
+
- **Tham số:**
|
|
21
|
+
- `key` (String): Độ dài dưới 200 ký tự. Không chứa khoảng trắng, dấu ngoặc kép hoặc dấu gạch chéo.
|
|
22
|
+
- `shared` (Boolean, mặc định: `false`): Nếu `true`, dữ liệu có thể được truy cập bởi tất cả người dùng của artifact đó. Nếu `false`, chỉ người dùng hiện tại truy cập được.
|
|
23
|
+
- **Kết quả trả về:** `{ key, value, shared }` hoặc `null` nếu không tìm thấy key (hoặc báo lỗi nếu truy vấn thất bại).
|
|
24
|
+
|
|
25
|
+
### 2. `storage_set(key, value, shared?)`
|
|
26
|
+
- **Mục tiêu:** Lưu trữ hoặc cập nhật giá trị.
|
|
27
|
+
- **Tham số:**
|
|
28
|
+
- `key` (String)
|
|
29
|
+
- `value` (String/JSON): Dung lượng tối đa 5MB.
|
|
30
|
+
- `shared` (Boolean)
|
|
31
|
+
- **Kết quả trả về:** `{ key, value, shared }` khi thành công.
|
|
32
|
+
|
|
33
|
+
### 3. `storage_delete(key, shared?)`
|
|
34
|
+
- **Mục tiêu:** Xóa key.
|
|
35
|
+
- **Kết quả trả về:** `{ key, deleted: true, shared }` khi thành công.
|
|
36
|
+
|
|
37
|
+
### 4. `storage_list(prefix?, shared?)`
|
|
38
|
+
- **Mục tiêu:** Liệt kê các key có prefix được chỉ định.
|
|
39
|
+
- **Kết quả trả về:** Danh sách các keys khớp.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 💾 Cơ chế Lưu trữ Cục bộ (Local Persistence Engine)
|
|
44
|
+
|
|
45
|
+
Tại môi trường runtime, dữ liệu được ánh xạ trực tiếp và lưu trữ bền vững theo hai phương án:
|
|
46
|
+
1. **SQLite Database (Sử dụng Symphony DB):** Lưu vào bảng `artifact_storage` với cấu trúc `(key TEXT PRIMARY KEY, value TEXT, shared INTEGER, updated_at TIMESTAMP)`.
|
|
47
|
+
2. **File JSON Cục bộ (Chế độ offline):** Lưu trực tiếp vào `.brain/storage.json` của dự án hiện tại.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 💡 Best Practices cho Thiết kế Artifacts
|
|
52
|
+
|
|
53
|
+
- **Đặt key theo cấp bậc (Hierarchical Keys):** Sử dụng dạng `table_name:record_id` (ví dụ: `todos:todo_1`, `settings:global`).
|
|
54
|
+
- **Gom nhóm dữ liệu (Batching):** Hạn chế gọi API ghi/đọc liên tục. Hãy gom các dữ liệu liên quan vào cùng một object JSON lớn để lưu trữ trong một key duy nhất (ví dụ: thay vì lưu từng pixel art, hãy lưu cả ma trận pixel của bảng).
|
|
55
|
+
- **Xử lý lỗi (Error Handling):** Bắt buộc sử dụng khối `try-catch` khi gọi các hàm storage. Hiển thị loading indicators hoặc giao diện mềm dẻo cho người dùng thay vì block UI khi truy xuất dữ liệu thất bại.
|
|
@@ -136,6 +136,28 @@ Projects được lưu tại `~/ShortMaker-Projects/<project-id>/`:
|
|
|
136
136
|
└── final/ # Final mixed output
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
## 🎞️ Video Editing & Rendering Engine (macOS & Node.js)
|
|
140
|
+
|
|
141
|
+
Skill này tích hợp bộ biên tập video tự động thay thế cho pipeline PowerShell cũ, tương thích hoàn toàn với macOS:
|
|
142
|
+
|
|
143
|
+
### 1. Storyboard Bridge (`scripts/shortmaker-bridge.js`)
|
|
144
|
+
Dùng để chuyển đổi `storyboard.json` từ ShortMaker sang định dạng EDL chuẩn:
|
|
145
|
+
- **Tải nhạc từ Pixabay**: Tự động tìm kiếm video chủ đề background music, tải xuống và trích xuất audio thành `music.mp3` nếu có `PIXABAY_API_KEY` (fallback về SoundHelix nhạc royalty-free).
|
|
146
|
+
- **Tự động hoạt ảnh hóa (Screenshot Animation)**: Nếu không có video segment trong `segments/`, bridge sẽ sử dụng thư viện `effects.js` áp dụng bộ lọc Ken Burns (`zoom-in`, `zoom-out`, `pan-left`, `pan-right`) biến các screenshot tĩnh (`image`) thành video clip.
|
|
147
|
+
- **Audio Ducking**: Tự động dìm nhạc nền (xuống `0.15`) trong suốt thời gian có giọng đọc TTS.
|
|
148
|
+
|
|
149
|
+
**Cách chạy:**
|
|
150
|
+
```bash
|
|
151
|
+
node scripts/shortmaker-bridge.js --project-dir=<path-to-shortmaker-project> --out-dir=<path-to-videomix-output> [--bgm-volume=0.2]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 2. VideoMix CLI (`scripts/videomix.js`)
|
|
155
|
+
Bộ CLI lõi để bootstrap, render và QA video:
|
|
156
|
+
- **Tạo dự án**: `node scripts/videomix.js new:cinematic <edl.json> <out-dir> --force` (tạo thư mục, cắt/chuẩn hóa âm thanh và video shot, tạo index.html).
|
|
157
|
+
- **Render**: `node scripts/videomix.js render <project-dir>` (chạy hyperframes lint, validate, render).
|
|
158
|
+
- **QA**: `node scripts/videomix.js qa <project-dir>` (phát hiện frame đen blackdetect và chụp ảnh kiểm chứng).
|
|
159
|
+
- **Video dọc**: `node scripts/videomix.js vertical <input.mp4> <output.mp4>` (sinh video 9:16 với blur background).
|
|
160
|
+
|
|
139
161
|
## 🔧 MCP Tools Reference
|
|
140
162
|
|
|
141
163
|
| Tool | Mục đích |
|
|
@@ -148,3 +170,4 @@ Projects được lưu tại `~/ShortMaker-Projects/<project-id>/`:
|
|
|
148
170
|
| `shortmaker_get_storyboard` | Xem toàn bộ storyboard |
|
|
149
171
|
| `shortmaker_trigger_render` | Bắt đầu render pipeline (async, chạy ngầm) |
|
|
150
172
|
| `shortmaker_get_render_status` | Kiểm tra tiến trình render đang chạy (polling) |
|
|
173
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate the zoompan filter based on selected effect.
|
|
5
|
+
*/
|
|
6
|
+
function getZoompanFilter({ effect = 'zoom-in', durationSec = 5, fps = 30, width = 1080, height = 1920 }) {
|
|
7
|
+
const totalFrames = Math.ceil(durationSec * fps);
|
|
8
|
+
let filter = '';
|
|
9
|
+
|
|
10
|
+
switch (effect) {
|
|
11
|
+
case 'zoom-in':
|
|
12
|
+
filter = `zoompan=z='min(zoom+0.001,1.15)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)'`;
|
|
13
|
+
break;
|
|
14
|
+
case 'zoom-out':
|
|
15
|
+
filter = `zoompan=z='max(1.15-0.001*on,1.0)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)'`;
|
|
16
|
+
break;
|
|
17
|
+
case 'pan-left':
|
|
18
|
+
filter = `zoompan=z='1.2':x='(1.2-1)*iw * (1 - on/${totalFrames})':y='ih/2-(ih/zoom/2)'`;
|
|
19
|
+
break;
|
|
20
|
+
case 'pan-right':
|
|
21
|
+
filter = `zoompan=z='1.2':x='(1.2-1)*iw * (on/${totalFrames})':y='ih/2-(ih/zoom/2)'`;
|
|
22
|
+
break;
|
|
23
|
+
case 'still':
|
|
24
|
+
default:
|
|
25
|
+
return `scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height},format=yuv420p`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `${filter}:d=${totalFrames}:s=${width}x${height},format=yuv420p`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Animate a static image into a short MP4 clip using FFmpeg.
|
|
33
|
+
*/
|
|
34
|
+
function animateScreenshot(inputPath, outputPath, options = {}) {
|
|
35
|
+
const {
|
|
36
|
+
effect = 'zoom-in',
|
|
37
|
+
durationSec = 5,
|
|
38
|
+
fps = 30,
|
|
39
|
+
width = 1080,
|
|
40
|
+
height = 1920
|
|
41
|
+
} = options;
|
|
42
|
+
|
|
43
|
+
const filter = getZoompanFilter({ effect, durationSec, fps, width, height });
|
|
44
|
+
const cmd = `ffmpeg -hide_banner -y -loop 1 -i "${inputPath}" -vf "${filter}" -c:v libx264 -preset veryfast -crf 20 -t ${durationSec} -pix_fmt yuv420p "${outputPath}"`;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw new Error(`FFmpeg animateScreenshot failed: ${err.stderr ? err.stderr.toString() : err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
animateScreenshot,
|
|
55
|
+
getZoompanFilter
|
|
56
|
+
};
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const { animateScreenshot } = require('./effects');
|
|
5
|
+
|
|
6
|
+
// Fallback music URL if Pixabay key is missing or API fails
|
|
7
|
+
const FALLBACK_MUSIC_URL = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Helper to fetch and download a file.
|
|
11
|
+
*/
|
|
12
|
+
async function downloadFile(url, destPath) {
|
|
13
|
+
const res = await fetch(url);
|
|
14
|
+
if (!res.ok) throw new Error(`Failed to download from ${url}: ${res.statusText}`);
|
|
15
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
16
|
+
fs.writeFileSync(destPath, buffer);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Locate Pixabay API Key from env, project .env, or global config.
|
|
21
|
+
*/
|
|
22
|
+
function getPixabayApiKey() {
|
|
23
|
+
if (process.env.PIXABAY_API_KEY) return process.env.PIXABAY_API_KEY;
|
|
24
|
+
if (process.env.PIXABAY_KEY) return process.env.PIXABAY_KEY;
|
|
25
|
+
|
|
26
|
+
// Check local .env
|
|
27
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
28
|
+
if (fs.existsSync(envPath)) {
|
|
29
|
+
try {
|
|
30
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
31
|
+
const match = content.match(/^PIXABAY_API_KEY\s*=\s*(.+)$/m);
|
|
32
|
+
if (match) return match[1].trim().replace(/['"]/g, '');
|
|
33
|
+
} catch (_) {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Search Pixabay for videos matching a background music query, and return a download URL.
|
|
41
|
+
*/
|
|
42
|
+
async function fetchPixabayBgmUrl(apiKey) {
|
|
43
|
+
const queries = ['lofi+beats', 'ambient+background+music', 'instrumental+music', 'cinematic+music'];
|
|
44
|
+
const query = queries[Math.floor(Math.random() * queries.length)];
|
|
45
|
+
const url = `https://pixabay.com/api/videos/?key=${apiKey}&q=${encodeURIComponent(query)}&per_page=10`;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch(url);
|
|
49
|
+
if (!res.ok) throw new Error(`Pixabay API status: ${res.status}`);
|
|
50
|
+
const data = await res.json();
|
|
51
|
+
|
|
52
|
+
if (data.hits && data.hits.length > 0) {
|
|
53
|
+
const hit = data.hits[Math.floor(Math.random() * data.hits.length)];
|
|
54
|
+
const videoUrl = hit.videos.medium?.url || hit.videos.small?.url || hit.videos.large?.url;
|
|
55
|
+
if (videoUrl) {
|
|
56
|
+
console.log(`[Bridge] Found Pixabay video clip for BGM: ${hit.pageURL}`);
|
|
57
|
+
return { videoUrl, isVideo: true };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.warn(`[Bridge] Warning: Pixabay search failed: ${err.message}`);
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get media duration using ffprobe.
|
|
68
|
+
*/
|
|
69
|
+
function getMediaDuration(filepath) {
|
|
70
|
+
try {
|
|
71
|
+
const output = execSync(
|
|
72
|
+
`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${filepath}"`,
|
|
73
|
+
{ encoding: 'utf-8', timeout: 5000 }
|
|
74
|
+
).trim();
|
|
75
|
+
return parseFloat(output) || 0;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Map storyboard transition effect to effects.js standard animations.
|
|
83
|
+
*/
|
|
84
|
+
function mapEffect(effectName) {
|
|
85
|
+
if (!effectName) return 'zoom-in';
|
|
86
|
+
const norm = effectName.toLowerCase();
|
|
87
|
+
if (norm.includes('push_in') || norm.includes('zoom_in') || norm.includes('zoom-in')) {
|
|
88
|
+
return 'zoom-in';
|
|
89
|
+
}
|
|
90
|
+
if (norm.includes('zoom_out') || norm.includes('zoom-out') || norm.includes('pulse')) {
|
|
91
|
+
return 'zoom-out';
|
|
92
|
+
}
|
|
93
|
+
if (norm.includes('pan') && (norm.includes('left') || norm.includes('vertical'))) {
|
|
94
|
+
return 'pan-left';
|
|
95
|
+
}
|
|
96
|
+
if (norm.includes('pan') && norm.includes('right')) {
|
|
97
|
+
return 'pan-right';
|
|
98
|
+
}
|
|
99
|
+
return 'still';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Run the Bridge process.
|
|
104
|
+
*/
|
|
105
|
+
async function runBridge({ shortmakerDir, outDir, bgmVolume = 0.2 }) {
|
|
106
|
+
console.log(`[Bridge] Importing ShortMaker project from: ${shortmakerDir}`);
|
|
107
|
+
|
|
108
|
+
const sbPath = path.join(shortmakerDir, 'storyboard.json');
|
|
109
|
+
if (!fs.existsSync(sbPath)) {
|
|
110
|
+
throw new Error(`storyboard.json not found in ${shortmakerDir}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const sb = JSON.parse(fs.readFileSync(sbPath, 'utf8'));
|
|
114
|
+
const scenes = sb.scenes || [];
|
|
115
|
+
if (scenes.length === 0) {
|
|
116
|
+
throw new Error('Storyboard has no scenes.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Ensure output directories exist
|
|
120
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
121
|
+
const mediaDir = path.join(outDir, 'media');
|
|
122
|
+
fs.mkdirSync(mediaDir, { recursive: true });
|
|
123
|
+
|
|
124
|
+
// 1. Identify/Download Music BGM
|
|
125
|
+
const bgmOut = path.join(mediaDir, 'music.mp3');
|
|
126
|
+
let bgmSourceUsed = 'fallback';
|
|
127
|
+
|
|
128
|
+
const apiKey = getPixabayApiKey();
|
|
129
|
+
if (apiKey) {
|
|
130
|
+
console.log('[Bridge] Pixabay API Key found. Searching Pixabay for BGM source...');
|
|
131
|
+
const bgmData = await fetchPixabayBgmUrl(apiKey);
|
|
132
|
+
if (bgmData) {
|
|
133
|
+
const tempVideo = path.join(outDir, 'temp_bgm_video.mp4');
|
|
134
|
+
try {
|
|
135
|
+
console.log(`[Bridge] Downloading Pixabay video clip: ${bgmData.videoUrl}`);
|
|
136
|
+
await downloadFile(bgmData.videoUrl, tempVideo);
|
|
137
|
+
|
|
138
|
+
console.log('[Bridge] Extracting audio track to bgm.mp3...');
|
|
139
|
+
execSync(`ffmpeg -hide_banner -y -i "${tempVideo}" -vn -c:a libmp3lame -b:a 192k "${bgmOut}"`, { stdio: 'ignore' });
|
|
140
|
+
bgmSourceUsed = 'pixabay';
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.warn(`[Bridge] Pixabay download/extract failed: ${err.message}. Falling back to default.`);
|
|
143
|
+
} finally {
|
|
144
|
+
if (fs.existsSync(tempVideo)) fs.unlinkSync(tempVideo);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (bgmSourceUsed === 'fallback') {
|
|
150
|
+
let localMusic = sb.audio?.music;
|
|
151
|
+
if (localMusic) {
|
|
152
|
+
if (!path.isAbsolute(localMusic)) {
|
|
153
|
+
localMusic = path.resolve(shortmakerDir, localMusic);
|
|
154
|
+
}
|
|
155
|
+
if (fs.existsSync(localMusic)) {
|
|
156
|
+
console.log(`[Bridge] Copying BGM from storyboard configuration: ${localMusic}`);
|
|
157
|
+
fs.copyFileSync(localMusic, bgmOut);
|
|
158
|
+
bgmSourceUsed = 'storyboard-local';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (bgmSourceUsed === 'fallback') {
|
|
163
|
+
console.log(`[Bridge] Downloading fallback BGM: ${FALLBACK_MUSIC_URL}`);
|
|
164
|
+
await downloadFile(FALLBACK_MUSIC_URL, bgmOut);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 2. Parse Scenes and build EDL
|
|
169
|
+
console.log('[Bridge] Mapping storyboard scenes to EDL format...');
|
|
170
|
+
const shots = [];
|
|
171
|
+
const dialogue = [];
|
|
172
|
+
const captions = [];
|
|
173
|
+
|
|
174
|
+
let currentTimelineStart = 0;
|
|
175
|
+
const duckingIntervals = [];
|
|
176
|
+
|
|
177
|
+
const screenshotsDir = sb.inputs?.screenshotsDir
|
|
178
|
+
? (path.isAbsolute(sb.inputs.screenshotsDir) ? sb.inputs.screenshotsDir : path.resolve(shortmakerDir, sb.inputs.screenshotsDir))
|
|
179
|
+
: path.join(shortmakerDir, 'assets');
|
|
180
|
+
|
|
181
|
+
for (let i = 0; i < scenes.length; i++) {
|
|
182
|
+
const scene = scenes[i];
|
|
183
|
+
const sceneIndexStr = String(i + 1).padStart(2, '0');
|
|
184
|
+
const id = scene.id || `shot-${sceneIndexStr}`;
|
|
185
|
+
|
|
186
|
+
let videoFile = path.join(shortmakerDir, 'segments', `${scene.id || `scene-${sceneIndexStr}`}.mp4`);
|
|
187
|
+
if (!fs.existsSync(videoFile)) {
|
|
188
|
+
videoFile = path.join(shortmakerDir, 'segments', `scene-${sceneIndexStr}.mp4`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const targetVideoFile = `shot-${sceneIndexStr}.mp4`;
|
|
192
|
+
const targetVideoPath = path.join(mediaDir, targetVideoFile);
|
|
193
|
+
let sceneDuration = parseFloat(scene.duration) || 5.0;
|
|
194
|
+
|
|
195
|
+
if (fs.existsSync(videoFile)) {
|
|
196
|
+
fs.copyFileSync(videoFile, targetVideoPath);
|
|
197
|
+
} else if (scene.image) {
|
|
198
|
+
const imagePath = path.isAbsolute(scene.image) ? scene.image : path.join(screenshotsDir, scene.image);
|
|
199
|
+
if (fs.existsSync(imagePath)) {
|
|
200
|
+
console.log(`[Bridge] Segment ${sceneIndexStr} not found. Animating static image: ${scene.image}`);
|
|
201
|
+
const animEffect = mapEffect(scene.effect);
|
|
202
|
+
animateScreenshot(imagePath, targetVideoPath, {
|
|
203
|
+
effect: animEffect,
|
|
204
|
+
durationSec: sceneDuration,
|
|
205
|
+
width: sb.project?.width || 1080,
|
|
206
|
+
height: sb.project?.height || 1920
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
console.warn(`[Bridge] Warning: Neither video segment nor screenshot found for scene: ${id}. A placeholder is needed.`);
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
console.warn(`[Bridge] Warning: No video segment or image found for scene: ${id}.`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let ttsFile = path.join(shortmakerDir, 'tts', `${scene.id || `scene-${sceneIndexStr}`}.mp3`);
|
|
216
|
+
if (!fs.existsSync(ttsFile)) {
|
|
217
|
+
ttsFile = path.join(shortmakerDir, 'tts', `scene-${sceneIndexStr}.mp3`);
|
|
218
|
+
}
|
|
219
|
+
if (!fs.existsSync(ttsFile)) {
|
|
220
|
+
ttsFile = path.join(shortmakerDir, 'tts', `scene-${sceneIndexStr}.wav`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const hasTts = fs.existsSync(ttsFile);
|
|
224
|
+
|
|
225
|
+
if (hasTts) {
|
|
226
|
+
const targetTtsFile = `dialogue-${sceneIndexStr}${path.extname(ttsFile)}`;
|
|
227
|
+
const targetTtsPath = path.join(mediaDir, targetTtsFile);
|
|
228
|
+
fs.copyFileSync(ttsFile, targetTtsPath);
|
|
229
|
+
|
|
230
|
+
const ttsDur = getMediaDuration(targetTtsPath);
|
|
231
|
+
if (ttsDur > 0) {
|
|
232
|
+
sceneDuration = ttsDur;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
dialogue.push({
|
|
236
|
+
id: `dialogue-${sceneIndexStr}`,
|
|
237
|
+
source: `./media/${targetTtsFile}`,
|
|
238
|
+
sourceStart: 0,
|
|
239
|
+
duration: sceneDuration,
|
|
240
|
+
timelineStart: currentTimelineStart,
|
|
241
|
+
volume: 1.0,
|
|
242
|
+
fadeIn: 0.05,
|
|
243
|
+
fadeOut: 0.12,
|
|
244
|
+
subtitle: scene.speech || ""
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
duckingIntervals.push({
|
|
248
|
+
start: currentTimelineStart,
|
|
249
|
+
end: currentTimelineStart + sceneDuration
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
shots.push({
|
|
254
|
+
id: id,
|
|
255
|
+
source: `./media/${targetVideoFile}`,
|
|
256
|
+
sourceStart: 0,
|
|
257
|
+
duration: sceneDuration,
|
|
258
|
+
timelineStart: currentTimelineStart
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (scene.speech || scene.text || scene.overlay) {
|
|
262
|
+
captions.push({
|
|
263
|
+
id: `caption-${sceneIndexStr}`,
|
|
264
|
+
timelineStart: currentTimelineStart,
|
|
265
|
+
duration: sceneDuration,
|
|
266
|
+
text: scene.speech || scene.text || scene.overlay
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
currentTimelineStart += sceneDuration;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const edlConfig = {
|
|
274
|
+
project: {
|
|
275
|
+
width: sb.project?.width || 1080,
|
|
276
|
+
height: sb.project?.height || 1920,
|
|
277
|
+
fps: sb.project?.fps || 30,
|
|
278
|
+
duration: currentTimelineStart,
|
|
279
|
+
title: sb.project?.name || "ShortMaker Mixed Video",
|
|
280
|
+
kicker: sb.project?.framework || "AIDA Promo"
|
|
281
|
+
},
|
|
282
|
+
music: {
|
|
283
|
+
path: "./media/music.mp3",
|
|
284
|
+
start: sb.audio?.start || 0,
|
|
285
|
+
duration: currentTimelineStart,
|
|
286
|
+
volume: parseFloat(bgmVolume) || 0.2,
|
|
287
|
+
fadeIn: sb.audio?.fadeIn !== undefined ? sb.audio.fadeIn : 1.0,
|
|
288
|
+
fadeOut: sb.audio?.fadeOut !== undefined ? sb.audio.fadeOut : 2.0,
|
|
289
|
+
ducking: duckingIntervals
|
|
290
|
+
},
|
|
291
|
+
shots: shots,
|
|
292
|
+
dialogue: dialogue,
|
|
293
|
+
captions: captions,
|
|
294
|
+
style: {
|
|
295
|
+
flashColors: ["#f7f0cf", "#93ffd4", "#fff0b4", "#ffd28b"]
|
|
296
|
+
},
|
|
297
|
+
assets: {
|
|
298
|
+
gsap: "./gsap.min.js"
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const edlPath = path.join(outDir, 'edl.json');
|
|
303
|
+
fs.writeFileSync(edlPath, JSON.stringify(edlConfig, null, 2), 'utf8');
|
|
304
|
+
console.log(`[Bridge] ✅ Successfully created EDL: ${edlPath}`);
|
|
305
|
+
console.log(`[Bridge] BGM source used: ${bgmSourceUsed}`);
|
|
306
|
+
console.log(`[Bridge] Total scenes: ${shots.length}, Duration: ${currentTimelineStart.toFixed(2)}s`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
module.exports = {
|
|
310
|
+
runBridge
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (require.main === module) {
|
|
314
|
+
const args = process.argv.slice(2);
|
|
315
|
+
const shortmakerDirArg = args.find(a => a.startsWith('--project-dir='))?.split('=')[1];
|
|
316
|
+
const outDirArg = args.find(a => a.startsWith('--out-dir='))?.split('=')[1];
|
|
317
|
+
const bgmVolumeArg = args.find(a => a.startsWith('--bgm-volume='))?.split('=')[1] || "0.2";
|
|
318
|
+
|
|
319
|
+
if (!shortmakerDirArg || !outDirArg) {
|
|
320
|
+
console.error('Usage: node scripts/shortmaker-bridge.js --project-dir=<path> --out-dir=<path> [--bgm-volume=0.2]');
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
runBridge({
|
|
325
|
+
shortmakerDir: path.resolve(shortmakerDirArg),
|
|
326
|
+
outDir: path.resolve(outDirArg),
|
|
327
|
+
bgmVolume: parseFloat(bgmVolumeArg)
|
|
328
|
+
}).catch(err => {
|
|
329
|
+
console.error(`[Bridge] Error: ${err.message}`);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
});
|
|
332
|
+
}
|