@leejungkiin/awkit 1.3.8 → 1.4.2
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 +630 -52
- package/bin/claude-generators.js +122 -0
- package/core/AGENTS.md +54 -0
- package/core/CLAUDE.md +155 -0
- package/core/GEMINI.md +44 -9
- package/core/GEMINI.md.bak +126 -199
- package/package.json +1 -1
- package/skills/ai-sprite-maker/SKILL.md +81 -0
- package/skills/ai-sprite-maker/scripts/animate_sprite.py +102 -0
- package/skills/ai-sprite-maker/scripts/process_sprites.py +140 -0
- package/skills/awf-session-restore/SKILL.md +12 -2
- package/skills/brainstorm-agent/SKILL.md +11 -8
- package/skills/code-review/SKILL.md +21 -33
- package/skills/gitnexus/gitnexus-cli/SKILL.md +82 -0
- package/skills/gitnexus/gitnexus-debugging/SKILL.md +89 -0
- package/skills/gitnexus/gitnexus-exploring/SKILL.md +78 -0
- package/skills/gitnexus/gitnexus-guide/SKILL.md +64 -0
- package/skills/gitnexus/gitnexus-impact-analysis/SKILL.md +97 -0
- package/skills/gitnexus/gitnexus-refactoring/SKILL.md +121 -0
- package/skills/lucylab-tts/SKILL.md +64 -0
- package/skills/lucylab-tts/resources/voices_library.json +908 -0
- package/skills/lucylab-tts/scripts/.env +1 -0
- package/skills/lucylab-tts/scripts/lucylab_tts.py +506 -0
- package/skills/nm-memory-sync/SKILL.md +14 -1
- package/skills/orchestrator/SKILL.md +5 -38
- package/skills/ship-to-code/SKILL.md +115 -0
- package/skills/short-maker/SKILL.md +150 -0
- package/skills/short-maker/_backup/storyboard.html +106 -0
- package/skills/short-maker/_backup/video_mixer.py +296 -0
- package/skills/short-maker/outputs/fitbite-promo/background.jpg +0 -0
- package/skills/short-maker/outputs/fitbite-promo/final/promo-final.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/script.md +19 -0
- package/skills/short-maker/outputs/fitbite-promo/segments/scene-01.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/segments/scene-02.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/segments/scene-03.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/segments/scene-04.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-01.png +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-02.png +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-03.png +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-04.png +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard.html +133 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard.json +38 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/merged_chroma.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/merged_crossfaded.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/ready_00.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/ready_01.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/ready_02.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/ready_03.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/manifest.json +31 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/scene-01.wav +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/scene-02.wav +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/scene-03.wav +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/scene-04.wav +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts_script.txt +11 -0
- package/skills/short-maker/scripts/google-flow-cli/.project-identity +41 -0
- package/skills/short-maker/scripts/google-flow-cli/.trae/rules/project_rules.md +52 -0
- package/skills/short-maker/scripts/google-flow-cli/CODEBASE.md +67 -0
- package/skills/short-maker/scripts/google-flow-cli/GoogleFlowCli.code-workspace +29 -0
- package/skills/short-maker/scripts/google-flow-cli/README.md +168 -0
- package/skills/short-maker/scripts/google-flow-cli/docs/specs/PROJECT.md +12 -0
- package/skills/short-maker/scripts/google-flow-cli/docs/specs/REQUIREMENTS.md +22 -0
- package/skills/short-maker/scripts/google-flow-cli/docs/specs/ROADMAP.md +16 -0
- package/skills/short-maker/scripts/google-flow-cli/docs/specs/TECH-SPEC.md +13 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/__init__.py +3 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/__init__.py +19 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/client.py +1921 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/models.py +64 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/rpc_ids.py +98 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/__init__.py +15 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/browser_auth.py +692 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/humanizer.py +417 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/proxy_ext.py +120 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/recaptcha.py +482 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/batchexecute/__init__.py +5 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/batchexecute/client.py +414 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/cli/__init__.py +1 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/cli/main.py +1075 -0
- package/skills/short-maker/scripts/google-flow-cli/pyproject.toml +36 -0
- package/skills/short-maker/scripts/google-flow-cli/script.txt +22 -0
- package/skills/short-maker/scripts/google-flow-cli/tests/__init__.py +0 -0
- package/skills/short-maker/scripts/google-flow-cli/tests/test_batchexecute.py +113 -0
- package/skills/short-maker/scripts/google-flow-cli/tests/test_client.py +190 -0
- package/skills/short-maker/templates/aida_script.md +40 -0
- package/skills/short-maker/templates/mimic_analyzer.md +29 -0
- package/skills/single-flow-task-execution/SKILL.md +412 -0
- package/skills/single-flow-task-execution/code-quality-reviewer-prompt.md +20 -0
- package/skills/single-flow-task-execution/implementer-prompt.md +78 -0
- package/skills/single-flow-task-execution/spec-reviewer-prompt.md +61 -0
- package/skills/skill-creator/SKILL.md +44 -0
- package/skills/spm-build-analysis/SKILL.md +92 -0
- package/skills/spm-build-analysis/references/build-optimization-sources.md +155 -0
- package/skills/spm-build-analysis/references/recommendation-format.md +85 -0
- package/skills/spm-build-analysis/references/spm-analysis-checks.md +105 -0
- package/skills/spm-build-analysis/scripts/check_spm_pins.py +118 -0
- package/skills/symphony-enforcer/SKILL.md +83 -97
- package/skills/symphony-orchestrator/SKILL.md +1 -1
- package/skills/trello-sync/SKILL.md +52 -45
- package/skills/verification-gate/SKILL.md +13 -2
- package/skills/xcode-build-benchmark/SKILL.md +88 -0
- package/skills/xcode-build-benchmark/references/benchmark-artifacts.md +94 -0
- package/skills/xcode-build-benchmark/references/benchmarking-workflow.md +67 -0
- package/skills/xcode-build-benchmark/schemas/build-benchmark.schema.json +230 -0
- package/skills/xcode-build-benchmark/scripts/benchmark_builds.py +308 -0
- package/skills/xcode-build-fixer/SKILL.md +218 -0
- package/skills/xcode-build-fixer/references/build-settings-best-practices.md +216 -0
- package/skills/xcode-build-fixer/references/fix-patterns.md +290 -0
- package/skills/xcode-build-fixer/references/recommendation-format.md +85 -0
- package/skills/xcode-build-fixer/scripts/benchmark_builds.py +308 -0
- package/skills/xcode-build-orchestrator/SKILL.md +156 -0
- package/skills/xcode-build-orchestrator/references/benchmark-artifacts.md +94 -0
- package/skills/xcode-build-orchestrator/references/build-settings-best-practices.md +216 -0
- package/skills/xcode-build-orchestrator/references/orchestration-report-template.md +143 -0
- package/skills/xcode-build-orchestrator/references/recommendation-format.md +85 -0
- package/skills/xcode-build-orchestrator/scripts/benchmark_builds.py +308 -0
- package/skills/xcode-build-orchestrator/scripts/diagnose_compilation.py +273 -0
- package/skills/xcode-build-orchestrator/scripts/generate_optimization_report.py +533 -0
- package/skills/xcode-compilation-analyzer/SKILL.md +89 -0
- package/skills/xcode-compilation-analyzer/references/build-optimization-sources.md +155 -0
- package/skills/xcode-compilation-analyzer/references/code-compilation-checks.md +106 -0
- package/skills/xcode-compilation-analyzer/references/recommendation-format.md +85 -0
- package/skills/xcode-compilation-analyzer/scripts/diagnose_compilation.py +273 -0
- package/skills/xcode-project-analyzer/SKILL.md +76 -0
- package/skills/xcode-project-analyzer/references/build-optimization-sources.md +155 -0
- package/skills/xcode-project-analyzer/references/build-settings-best-practices.md +216 -0
- package/skills/xcode-project-analyzer/references/project-audit-checks.md +101 -0
- package/skills/xcode-project-analyzer/references/recommendation-format.md +85 -0
- package/templates/CODEBASE.md +26 -42
- package/templates/configs/trello-config.json +2 -2
- package/templates/workflow_dual_mode_template.md +5 -5
- package/workflows/_uncategorized/conductor-codex.md +125 -0
- package/workflows/_uncategorized/conductor.md +97 -0
- package/workflows/_uncategorized/ship-to-code.md +85 -0
- package/workflows/_uncategorized/trello-sync.md +52 -0
- package/workflows/context/codebase-sync.md +10 -87
- package/workflows/quality/visual-debug.md +66 -12
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ship-to-code
|
|
3
|
+
description: >-
|
|
4
|
+
Universal Code Porting & Migration Specialist. Translates legacy or reference code
|
|
5
|
+
from ANY source language/framework to ANY target language/framework.
|
|
6
|
+
Rebuilds the architecture while adhering to the target's modern best practices.
|
|
7
|
+
author: Antigravity Team
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
trigger: conditional
|
|
10
|
+
activation_keywords:
|
|
11
|
+
- "/ship-to-code"
|
|
12
|
+
- "/port-code"
|
|
13
|
+
- "/migrate-code"
|
|
14
|
+
- "port code"
|
|
15
|
+
- "chuyển ngôn ngữ"
|
|
16
|
+
- "ship to code"
|
|
17
|
+
- "dịch code"
|
|
18
|
+
priority: high
|
|
19
|
+
platform: agnostic
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# 🚢 Ship-to-Code Skill
|
|
23
|
+
|
|
24
|
+
> **Purpose:** Transform reference codebase from ANY source language/framework to ANY modern target language/framework.
|
|
25
|
+
> **Philosophy:** "Read source to understand WHAT and WHY → Write target for HOW."
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## ⚠️ SCOPE CLARITY
|
|
30
|
+
|
|
31
|
+
| This skill DOES | This skill DOES NOT |
|
|
32
|
+
|-----------------|---------------------|
|
|
33
|
+
| Read & analyze source language code & structure | Write in the obsolete/source language |
|
|
34
|
+
| Rebuild logic idiomatically in modern target language | Blindly translate line-by-line (syntax-only) |
|
|
35
|
+
| Map source dependencies to target equivalents | Auto-migrate production database records directly |
|
|
36
|
+
| Implement Clean Architecture/Modern patterns in target | Just copy-paste without adapting paradigms |
|
|
37
|
+
| Extract/convert needed resources on-demand | Mass-copy entire resource folders blindly |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 🎯 ROLE DEFINITION
|
|
42
|
+
|
|
43
|
+
When this skill is active, the agent becomes:
|
|
44
|
+
|
|
45
|
+
> **Expert Multi-Language Porting Architect**
|
|
46
|
+
> - Master at deciphering unfamiliar, foreign or legacy codebases.
|
|
47
|
+
> - Fluent in modern target architectures (Clean Architecture / MVC / MVVM / Hexagonal depending on target ecosystem).
|
|
48
|
+
> - Knows how to map business logic across different language paradigms (e.g., Object-Oriented to Functional, Sync to Async, etc).
|
|
49
|
+
> - Enforces exact Input/Output mathematical parity for core algorithms and cryptology.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 📋 EXECUTION PIPELINE (6 Phases)
|
|
54
|
+
|
|
55
|
+
> **Rule:** Always complete one phase fully before moving to the next.
|
|
56
|
+
> **Rule:** After each phase, create a checkpoint summary for the user to approve.
|
|
57
|
+
|
|
58
|
+
### Phase 0: Ecosystem & Dependency Mapping 🔍
|
|
59
|
+
**Purpose:** Identify all 3rd-party libraries, SDKs, and frameworks in the source project and map them to the best modern equivalents in the target ecosystem.
|
|
60
|
+
1. Scan source project configuration files (`package.json`, `build.gradle`, `requirements.txt`, `Cargo.toml`, `go.mod`, etc.).
|
|
61
|
+
2. Generate a Library Detection Report featuring a **Matrix (Source Lib → Target Lib)**.
|
|
62
|
+
3. Present to the user for evaluation and approval.
|
|
63
|
+
|
|
64
|
+
### Phase 1: Architecture Design & Project Bootstrap 📄
|
|
65
|
+
**Purpose:** Analyze application entry points, metadata, lifecycle, and propose a robust target directory structure.
|
|
66
|
+
1. Identify how the app starts, handles authentication, routes traffic, and loads plugins.
|
|
67
|
+
2. Propose a modern project folder layout aligned with target language standards (e.g., standard Go layout, feature-first React layout, Clean Architecture for Mobile).
|
|
68
|
+
3. Scaffold initial configuration files for the target language.
|
|
69
|
+
|
|
70
|
+
### Phase 2: Data & Domain Layer Reconstruction 💾
|
|
71
|
+
**Purpose:** Rebuild strict data contracts and persistence infrastructure.
|
|
72
|
+
1. Convert source models/POJOs/Entities into target native DTOs, interfaces, structs, or dataclasses (e.g., implementing `.fromJson()`, `Codable`, `serde`).
|
|
73
|
+
2. Port Database schemas/ORMs to target paradigms (e.g., translating SQLAlchemy to Prisma, Room to SwiftData).
|
|
74
|
+
3. Migrate API clients using target's native concurrency mechanisms (Coroutines, `async/await`, Goroutines).
|
|
75
|
+
|
|
76
|
+
### Phase 3: Core Business Logic & Utils 🧮
|
|
77
|
+
**Purpose:** Port specialized algorithms, encryption, math, and custom helpers.
|
|
78
|
+
1. Translate raw logic with strict adherence to the exact mathematical and state behavior of the source.
|
|
79
|
+
2. Provide **Unit Tests** in the target language to prove 100% computational parity with source output (especially for Base64, MD5/SHA, AES, timezone parsing).
|
|
80
|
+
|
|
81
|
+
### Phase 4: UI & Presentation / Controller Layer 🎨
|
|
82
|
+
**Purpose:** Rebuild user interfaces or API controllers utilizing the target's standard frameworks.
|
|
83
|
+
1. Map source UI components to target equivalents (e.g., React to Compose, HTML/Jinja to Vue, Android XML to SwiftUI).
|
|
84
|
+
2. For backend APIs: Convert source controller route handling to modern target framework routing (e.g., Express.js to FastAPI, Spring Boot to Go Gin).
|
|
85
|
+
3. Implement modern state management and reactive data flows native to the new ecosystem.
|
|
86
|
+
|
|
87
|
+
### Phase 5: SDK Integration & Parity Quality Gate ✅
|
|
88
|
+
**Purpose:** Finalize third-party setups and ensure feature completeness.
|
|
89
|
+
1. Wire up heavy SDKs (Auth, Analytics, Push Notifications, Payment gateways) with target SDKs.
|
|
90
|
+
2. Perform rigorous Parity Validation across:
|
|
91
|
+
- *Branch Coverage:* Ensure all `if/switch` edge cases from source were ported.
|
|
92
|
+
- *Endpoint Parity:* Ensure headers, bodies, status codes match output.
|
|
93
|
+
- *Visual Parity:* (If UI) Layout behaves correctly.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 🚫 ANTI-PATTERNS
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
never_do:
|
|
101
|
+
- Line-by-line verbatim syntax translation (e.g., writing Java code in Go syntax using 'for' instead of 'range', or ignoring Swift optionals to force unwrap like in C#).
|
|
102
|
+
- Use deprecated patterns in the target ecosystem just because the source used them.
|
|
103
|
+
- Skip extracting business rules before writing the target implementation.
|
|
104
|
+
- Alter encryption hashes / outputs — they must match the original exactly for server compatibility!
|
|
105
|
+
|
|
106
|
+
always_do:
|
|
107
|
+
- Write Idiomatic Code: Fully embrace the target language's design patterns, conventions, and error-handling features.
|
|
108
|
+
- Checkpoint and halt after generating the Ecosystem Dependency Matrix to let the user review framework choices.
|
|
109
|
+
- Build test suites to verify math and crypto translations against known source outputs.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
*ship-to-code v1.0.0 — Universal Code Porting Skill*
|
|
115
|
+
*Created by Antigravity Team*
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: short-maker
|
|
3
|
+
description: |
|
|
4
|
+
Đạo diễn sản xuất video quảng cáo App (App Promo) bằng AI. Tự động hóa quy trình viết kịch bản AIDA,
|
|
5
|
+
quản lý storyboard qua ShortMaker Studio MCP Server, và gọi Google Flow (Veo 3) render video bối cảnh thật
|
|
6
|
+
kèm Native Voice. Hỗ trợ Mimic Mode bóc tách nội dung trending từ YouTube/TikTok.
|
|
7
|
+
metadata:
|
|
8
|
+
stage: workflow
|
|
9
|
+
version: "2.0"
|
|
10
|
+
requires: "ShortMaker Studio (MCP Server), google-flow-cli, ffmpeg"
|
|
11
|
+
tags: [video, marketing, ads, app-promo, veo, gflow, tiktok, youtube, shorts, mcp]
|
|
12
|
+
trigger: explicit
|
|
13
|
+
activation_keywords:
|
|
14
|
+
- "/short"
|
|
15
|
+
- "/promo"
|
|
16
|
+
- "/mimic"
|
|
17
|
+
- "làm video tiktok"
|
|
18
|
+
- "chạy video flow"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# 🎬 Short Maker v2.0 (MCP Client Mode)
|
|
22
|
+
|
|
23
|
+
> **Mục tiêu**: Tối đa hóa traffic miễn phí từ Video ngắn (TikTok, YouTube Shorts, Reels)
|
|
24
|
+
> bằng cách tự động sản xuất video quảng cáo App với cấu trúc AIDA hoặc copy (Mimic) hook viral.
|
|
25
|
+
|
|
26
|
+
## ⚠️ Prerequisites (BẮT BUỘC)
|
|
27
|
+
|
|
28
|
+
Skill này hoạt động như **MCP Client** — mọi thao tác project/storyboard/render đều gọi qua **ShortMaker Studio MCP Server**.
|
|
29
|
+
|
|
30
|
+
**Trước khi bắt đầu, KIỂM TRA:**
|
|
31
|
+
1. ShortMaker Studio MCP Server đang chạy (IDE đã kết nối qua MCP config)
|
|
32
|
+
2. Gọi thử `shortmaker_list_projects` — nếu thành công → MCP Server OK
|
|
33
|
+
3. Google Flow auth: kiểm tra `~/.gflow/env` tồn tại. Nếu chưa:
|
|
34
|
+
```bash
|
|
35
|
+
cd ~/Dev2/MacOS/Shortmaker/scripts/google-flow-cli && PYTHONPATH=. python3 gflow/cli/main.py auth
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 📡 Chế độ hoạt động (Triggers)
|
|
39
|
+
|
|
40
|
+
- **Original Mode (`/promo`, `/short`)**: User nhập [Tên App + Tính năng]. AI đóng vai Giám đốc Marketing, lên kịch bản AIDA.
|
|
41
|
+
- **Mimic Mode (`/mimic`)**: User cung cấp [Link YouTube/TikTok]. AI trích xuất transcript, phân tích Hook/Pacing, sau đó clone cấu trúc kịch bản đó nhưng áp dụng cho App của user.
|
|
42
|
+
|
|
43
|
+
## 🧱 Quy trình hoạt động (Mandatory Flow)
|
|
44
|
+
|
|
45
|
+
**Bảo vệ Credit**: Mọi video Veo 3 đều tốn 20 credit. TUYỆT ĐỐI không gọi render khi chưa có xác nhận từ người dùng qua bước Storyboard Review.
|
|
46
|
+
|
|
47
|
+
### Giai đoạn 1: Bootstrap Project
|
|
48
|
+
|
|
49
|
+
Gọi MCP tool để tạo project:
|
|
50
|
+
```
|
|
51
|
+
shortmaker_create_project(name: "FitWitness Promo", appName: "FitWitness", description: "30s TikTok promo")
|
|
52
|
+
```
|
|
53
|
+
→ Trả về `projectId` và `path`. Dùng `projectId` cho tất cả các bước sau.
|
|
54
|
+
|
|
55
|
+
### Giai đoạn 2: Kịch Bản & Character Casting
|
|
56
|
+
|
|
57
|
+
1. Sinh kịch bản `script.md` (dùng template `aida_script.md` hoặc `mimic_analyzer.md`).
|
|
58
|
+
2. **Character Setup** — gọi MCP tool:
|
|
59
|
+
```
|
|
60
|
+
shortmaker_setup_character(projectId: "...", prompt: "A young Vietnamese woman, long black hair...", seed: "123456")
|
|
61
|
+
```
|
|
62
|
+
3. **Xử lý ảnh tham chiếu**:
|
|
63
|
+
- User cung cấp ảnh → AI phân tích, viết `character_prompt`, copy ảnh vào project dir
|
|
64
|
+
- User không có ảnh → AI thiết kế character, gọi `generate-image` tạo mẫu
|
|
65
|
+
4. **Actor Approval**: Trình bày ảnh cho User. CHỈ TIẾP TỤC khi User chốt nhân vật.
|
|
66
|
+
|
|
67
|
+
### Giai đoạn 3: Storyboard (0 Cost)
|
|
68
|
+
|
|
69
|
+
Gọi MCP tool lặp lại cho từng scene:
|
|
70
|
+
```
|
|
71
|
+
shortmaker_add_scene(
|
|
72
|
+
projectId: "...",
|
|
73
|
+
prompt: "A woman standing in a modern gym, looking at her phone...",
|
|
74
|
+
speech: "Tired of forgetting your workouts?",
|
|
75
|
+
duration: 8,
|
|
76
|
+
transition: "fade",
|
|
77
|
+
sceneType: "hook"
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Quy tắc prompt**:
|
|
82
|
+
- **ƯU TIÊN MÔI TRƯỜNG THẬT**: TUYỆT ĐỐI KHÔNG dùng Greenscreen/Chroma key mặc định
|
|
83
|
+
- Character prompt sẽ được auto-prepend bởi MCP Server
|
|
84
|
+
- BẮT BUỘC truyền `--seed` đã chốt khi render
|
|
85
|
+
|
|
86
|
+
Sau khi thêm đủ scenes, hướng dẫn User mở **ShortMaker Studio** để review storyboard trực quan.
|
|
87
|
+
Hoặc AI tự review bằng:
|
|
88
|
+
```
|
|
89
|
+
shortmaker_get_storyboard(projectId: "...")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Sửa scene nếu cần:
|
|
93
|
+
```
|
|
94
|
+
shortmaker_update_scene(projectId: "...", sceneId: "scene-01", speech: "Updated narration")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**CHỈ TIẾP TỤC khi User xác nhận đã duyệt xong Storyboard.**
|
|
98
|
+
|
|
99
|
+
### Giai đoạn 4: Render (Batch)
|
|
100
|
+
|
|
101
|
+
Gọi MCP tool:
|
|
102
|
+
```
|
|
103
|
+
shortmaker_trigger_render(projectId: "...", fadeDuration: 1.0, bgmVolume: 0.1)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
User có thể theo dõi tiến trình trên ShortMaker Studio GUI.
|
|
107
|
+
|
|
108
|
+
## 🔀 Hiệu ứng Chuyển Cảnh (Transitions)
|
|
109
|
+
|
|
110
|
+
Các hiệu ứng được hỗ trợ (truyền vào `transition` khi `add_scene`):
|
|
111
|
+
- `fade` — Chuyển mờ dần (mặc định)
|
|
112
|
+
- `slideleft` / `slideright` — Trượt sang trái/phải
|
|
113
|
+
- `wipeleft` / `wiperight` — Quét sang trái/phải
|
|
114
|
+
- `circlecrop` — Thu nhỏ hình tròn
|
|
115
|
+
- `dissolve` — Hòa tan
|
|
116
|
+
- `none` — Cắt thẳng, không hiệu ứng
|
|
117
|
+
|
|
118
|
+
## 🌿 Green Screen (Fallback Option)
|
|
119
|
+
|
|
120
|
+
Nếu User ĐẶC BIỆT yêu cầu greenscreen:
|
|
121
|
+
- Thêm "on a solid chroma green screen background" vào prompt scene
|
|
122
|
+
- Render pipeline sẽ tự xử lý chroma key với background được cung cấp
|
|
123
|
+
|
|
124
|
+
## 📁 Output Convention
|
|
125
|
+
|
|
126
|
+
Projects được lưu tại `~/ShortMaker-Projects/<project-id>/`:
|
|
127
|
+
```
|
|
128
|
+
<project-id>/
|
|
129
|
+
├── shortmaker.config.json # Project config (auto-managed)
|
|
130
|
+
├── storyboard.json # Scene data (auto-managed)
|
|
131
|
+
├── assets/ # Character ref, BGM
|
|
132
|
+
├── storyboard/ # Scene preview images
|
|
133
|
+
├── segments/ # Rendered video segments
|
|
134
|
+
├── tts/ # TTS audio files
|
|
135
|
+
├── temp/ # Temporary processing files
|
|
136
|
+
└── final/ # Final mixed output
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 🔧 MCP Tools Reference
|
|
140
|
+
|
|
141
|
+
| Tool | Mục đích |
|
|
142
|
+
|------|----------|
|
|
143
|
+
| `shortmaker_list_projects` | Liệt kê projects hiện có |
|
|
144
|
+
| `shortmaker_create_project` | Tạo project mới |
|
|
145
|
+
| `shortmaker_setup_character` | Chốt nhân vật (prompt + seed) |
|
|
146
|
+
| `shortmaker_add_scene` | Thêm scene vào storyboard |
|
|
147
|
+
| `shortmaker_update_scene` | Sửa scene đã có |
|
|
148
|
+
| `shortmaker_get_storyboard` | Xem toàn bộ storyboard |
|
|
149
|
+
| `shortmaker_trigger_render` | Bắt đầu render pipeline (async, chạy ngầm) |
|
|
150
|
+
| `shortmaker_get_render_status` | Kiểm tra tiến trình render đang chạy (polling) |
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="vi">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Storyboard Preview</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #121212; --card-bg: #1E1E1E; --text: #E0E0E0; --accent: #BB86FC; --accent-hover: #9965f4;
|
|
10
|
+
}
|
|
11
|
+
body {
|
|
12
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
13
|
+
background-color: var(--bg); color: var(--text); padding: 20px;
|
|
14
|
+
}
|
|
15
|
+
header { text-align: center; margin-bottom: 30px; }
|
|
16
|
+
h1 { margin: 0; color: var(--accent); }
|
|
17
|
+
.meta { color: #888; font-size: 0.9em; margin-top: 5px; }
|
|
18
|
+
.grid {
|
|
19
|
+
display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px;
|
|
20
|
+
}
|
|
21
|
+
.card {
|
|
22
|
+
background: var(--card-bg); border-radius: 12px; overflow: hidden;
|
|
23
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.3); border: 1px solid #333;
|
|
24
|
+
}
|
|
25
|
+
.card-img-wrapper { position: relative; padding-top: 56.25%; /* 16:9 Aspect Ratio */ background: #000; }
|
|
26
|
+
.card img {
|
|
27
|
+
position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;
|
|
28
|
+
}
|
|
29
|
+
.scene-number {
|
|
30
|
+
position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.7);
|
|
31
|
+
color: #fff; padding: 4px 8px; border-radius: 6px; font-weight: bold; font-size: 0.8em;
|
|
32
|
+
}
|
|
33
|
+
.card-content { padding: 15px; }
|
|
34
|
+
.duration { color: var(--accent); font-weight: bold; font-size: 0.9em; margin-bottom: 8px; }
|
|
35
|
+
.script { font-size: 1em; line-height: 1.4; margin-bottom: 12px; }
|
|
36
|
+
.prompt { font-size: 0.8em; color: #888; font-style: italic; background: #2a2a2a; padding: 8px; border-radius: 6px; }
|
|
37
|
+
.timeline-bar {
|
|
38
|
+
height: 6px; background: #333; margin-top: 30px; border-radius: 3px; position: relative; overflow: hidden;
|
|
39
|
+
}
|
|
40
|
+
.timeline-progress { height: 100%; background: var(--accent); width: 0%; }
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body>
|
|
44
|
+
|
|
45
|
+
<header>
|
|
46
|
+
<h1 id="title">Loading Storyboard...</h1>
|
|
47
|
+
<div class="meta" id="meta">Duration: 0s</div>
|
|
48
|
+
</header>
|
|
49
|
+
|
|
50
|
+
<div class="timeline-bar">
|
|
51
|
+
<div class="timeline-progress" id="timeline"></div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="grid" id="grid">
|
|
55
|
+
<!-- Cards will be injected here -->
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<script>
|
|
59
|
+
async function loadStoryboard() {
|
|
60
|
+
try {
|
|
61
|
+
// Fetch expects storyboard.json in the same directory
|
|
62
|
+
const response = await fetch('storyboard.json');
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
|
|
65
|
+
document.getElementById('title').textContent = data.title || 'App Promo Storyboard';
|
|
66
|
+
document.getElementById('meta').textContent = `Total Duration: ${data.duration || 0}s | Video Target: ${data.target_format || '16:9'}`;
|
|
67
|
+
|
|
68
|
+
const grid = document.getElementById('grid');
|
|
69
|
+
grid.innerHTML = '';
|
|
70
|
+
|
|
71
|
+
let totalTime = data.duration || 1; // avoid /0
|
|
72
|
+
let cumulative = 0;
|
|
73
|
+
|
|
74
|
+
data.scenes.forEach((scene, index) => {
|
|
75
|
+
cumulative += scene.duration;
|
|
76
|
+
|
|
77
|
+
const card = document.createElement('div');
|
|
78
|
+
card.className = 'card';
|
|
79
|
+
card.innerHTML = `
|
|
80
|
+
<div class="card-img-wrapper">
|
|
81
|
+
<img src="${scene.image || 'https://via.placeholder.com/640x360?text=Generating+Image...'}" alt="Scene ${scene.id}">
|
|
82
|
+
<div class="scene-number">Scene ${scene.id}</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="card-content">
|
|
85
|
+
<div class="duration">⏱️ ${scene.duration}s</div>
|
|
86
|
+
<div class="script">🎙️ "${scene.script}"</div>
|
|
87
|
+
<div class="prompt">🎨 Prompt: ${scene.prompt}</div>
|
|
88
|
+
</div>
|
|
89
|
+
`;
|
|
90
|
+
grid.appendChild(card);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Setup dumb timeline animation for fun
|
|
94
|
+
const timeline = document.getElementById('timeline');
|
|
95
|
+
timeline.style.width = '100%';
|
|
96
|
+
timeline.style.transition = `width ${data.duration}s linear`;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
document.getElementById('title').innerHTML = `<span style="color:red">Error Loading storyboard.json</span>`;
|
|
99
|
+
console.error(e);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
loadStoryboard();
|
|
104
|
+
</script>
|
|
105
|
+
</body>
|
|
106
|
+
</html>
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import glob
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_duration(filepath: Path) -> float:
|
|
11
|
+
cmd = [
|
|
12
|
+
"ffprobe",
|
|
13
|
+
"-v", "error",
|
|
14
|
+
"-show_entries", "format=duration",
|
|
15
|
+
"-of", "default=noprint_wrappers=1:nokey=1",
|
|
16
|
+
str(filepath)
|
|
17
|
+
]
|
|
18
|
+
try:
|
|
19
|
+
output = subprocess.check_output(cmd, text=True).strip()
|
|
20
|
+
return float(output)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
print(f"Error getting duration for {filepath}: {e}")
|
|
23
|
+
return 0.0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def check_has_audio(filepath: Path) -> bool:
|
|
27
|
+
cmd = [
|
|
28
|
+
"ffprobe", "-v", "error",
|
|
29
|
+
"-select_streams", "a",
|
|
30
|
+
"-show_entries", "stream=codec_type",
|
|
31
|
+
"-of", "csv=p=0",
|
|
32
|
+
str(filepath)
|
|
33
|
+
]
|
|
34
|
+
try:
|
|
35
|
+
output = subprocess.check_output(cmd, text=True).strip()
|
|
36
|
+
return len(output) > 0
|
|
37
|
+
except Exception:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def ensure_dir(path: Path) -> Path:
|
|
42
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
return path
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Supported FFmpeg xfade transitions
|
|
47
|
+
SUPPORTED_TRANSITIONS = [
|
|
48
|
+
'fade', 'slideleft', 'slideright', 'circlecrop',
|
|
49
|
+
'dissolve', 'wipeleft', 'wiperight',
|
|
50
|
+
'smoothleft', 'smoothright', 'smoothup', 'smoothdown',
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def load_storyboard_transitions(project_dir: Path) -> list:
|
|
55
|
+
"""Read per-scene transitions from storyboard.json if available."""
|
|
56
|
+
sb_path = project_dir / "storyboard.json"
|
|
57
|
+
if not sb_path.exists():
|
|
58
|
+
return []
|
|
59
|
+
try:
|
|
60
|
+
with open(sb_path, 'r') as f:
|
|
61
|
+
data = json.load(f)
|
|
62
|
+
transitions = []
|
|
63
|
+
for scene in data.get('scenes', []):
|
|
64
|
+
t = scene.get('transition', 'fade')
|
|
65
|
+
if t not in SUPPORTED_TRANSITIONS:
|
|
66
|
+
t = 'fade'
|
|
67
|
+
transitions.append(t)
|
|
68
|
+
return transitions
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"Warning: Could not read storyboard.json: {e}")
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def main():
|
|
75
|
+
parser = argparse.ArgumentParser(description="Auto-Mixer cho Short Maker bằng FFmpeg")
|
|
76
|
+
parser.add_argument("--project-dir", required=True, help="Thư mục project chứa outputs (ví dụ: outputs/promo-app)")
|
|
77
|
+
parser.add_argument("--fade-duration", type=float, default=1.0, help="Thời gian crossfade giữa các cảnh (giây)")
|
|
78
|
+
parser.add_argument("--bgm-volume", type=float, default=0.1, help="Âm lượng nhạc nền (0.0 đến 1.0)")
|
|
79
|
+
parser.add_argument("--chroma-bg", type=str, help="Đường dẫn đến ảnh/video nền để thay thế phông xanh")
|
|
80
|
+
parser.add_argument("--chroma-color", type=str, default="0x00FF00", help="Mã màu phông xanh (mặc định: 0x00FF00)")
|
|
81
|
+
parser.add_argument("--chroma-sim", type=float, default=0.3, help="Độ tương đồng màu (0.01 - 1.0, mặc định: 0.3)")
|
|
82
|
+
parser.add_argument("--chroma-blend", type=float, default=0.2, help="Độ mượt viền (0.0 - 1.0, mặc định: 0.2)")
|
|
83
|
+
args = parser.parse_args()
|
|
84
|
+
|
|
85
|
+
project_dir = Path(args.project_dir)
|
|
86
|
+
segments_dir = project_dir / "segments"
|
|
87
|
+
tts_dir = project_dir / "tts"
|
|
88
|
+
audio_dir = project_dir / "audio"
|
|
89
|
+
temp_dir = ensure_dir(project_dir / "temp")
|
|
90
|
+
final_dir = ensure_dir(project_dir / "final")
|
|
91
|
+
|
|
92
|
+
if not segments_dir.exists() or not tts_dir.exists():
|
|
93
|
+
print(f"Error: Thư mục segments hoặc tts không tồn tại trong {project_dir}")
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
# Liệt kê các cảnh
|
|
97
|
+
video_files = sorted(glob.glob(str(segments_dir / "*.mp4")))
|
|
98
|
+
if not video_files:
|
|
99
|
+
print("Không tìm thấy video nào trong segments/")
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
scenes = []
|
|
103
|
+
for v_path in video_files:
|
|
104
|
+
v_path = Path(v_path)
|
|
105
|
+
scene_name = v_path.stem # vd: scene-01
|
|
106
|
+
|
|
107
|
+
# Tìm file TTS tương ứng (mp3 hoặc wav)
|
|
108
|
+
tts_path = tts_dir / f"{scene_name}.mp3"
|
|
109
|
+
if not tts_path.exists():
|
|
110
|
+
tts_path = tts_dir / f"{scene_name}.wav"
|
|
111
|
+
|
|
112
|
+
if not tts_path.exists():
|
|
113
|
+
print(f"Thêm cảnh {scene_name} nhưng KHÔNG có file âm thanh TTS.")
|
|
114
|
+
scenes.append({"video": v_path, "audio": None})
|
|
115
|
+
else:
|
|
116
|
+
scenes.append({"video": v_path, "audio": tts_path})
|
|
117
|
+
|
|
118
|
+
print(f"Bắt đầu mix {len(scenes)} cảnh...")
|
|
119
|
+
|
|
120
|
+
# Bước 1: Render từng cảnh riêng lẻ (Loop video để khớp với audio)
|
|
121
|
+
ready_scenes = []
|
|
122
|
+
for idx, scene in enumerate(scenes):
|
|
123
|
+
out_scene = temp_dir / f"ready_{idx:02d}.mp4"
|
|
124
|
+
ready_scenes.append(out_scene)
|
|
125
|
+
|
|
126
|
+
# Tiết kiệm thời gian nếu render rồi
|
|
127
|
+
if out_scene.exists():
|
|
128
|
+
print(f" - [Skip] Cảnh {idx+1} đã được chuẩn bị.")
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
print(f" - [Render] Chuẩn bị cảnh {idx+1}...")
|
|
132
|
+
if scene["audio"]:
|
|
133
|
+
# Dùng stream_loop để loop video nếu nó ngắn hơn audio
|
|
134
|
+
cmd = [
|
|
135
|
+
"ffmpeg", "-y",
|
|
136
|
+
"-stream_loop", "-1", "-i", str(scene["video"]),
|
|
137
|
+
"-i", str(scene["audio"]),
|
|
138
|
+
"-c:v", "libx264", "-c:a", "aac",
|
|
139
|
+
"-map", "0:v:0", "-map", "1:a:0",
|
|
140
|
+
"-shortest", "-pix_fmt", "yuv420p",
|
|
141
|
+
str(out_scene)
|
|
142
|
+
]
|
|
143
|
+
else:
|
|
144
|
+
# Ưu tiên Native Voice của Veo 3 nếu không có file TTS ngoài.
|
|
145
|
+
# Nếu video không có audio stream, thêm audio âm câm để mix tránh lỗi acrossfade.
|
|
146
|
+
has_audio = check_has_audio(scene["video"])
|
|
147
|
+
if has_audio:
|
|
148
|
+
cmd = [
|
|
149
|
+
"ffmpeg", "-y",
|
|
150
|
+
"-i", str(scene["video"]),
|
|
151
|
+
"-c:v", "libx264", "-c:a", "aac", "-pix_fmt", "yuv420p",
|
|
152
|
+
str(out_scene)
|
|
153
|
+
]
|
|
154
|
+
else:
|
|
155
|
+
cmd = [
|
|
156
|
+
"ffmpeg", "-y",
|
|
157
|
+
"-f", "lavfi", "-i", "anullsrc=channel_layout=stereo:sample_rate=44100",
|
|
158
|
+
"-i", str(scene["video"]),
|
|
159
|
+
"-c:v", "libx264", "-c:a", "aac",
|
|
160
|
+
"-map", "1:v:0", "-map", "0:a:0",
|
|
161
|
+
"-shortest", "-pix_fmt", "yuv420p",
|
|
162
|
+
str(out_scene)
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
|
166
|
+
|
|
167
|
+
# Bước 2: Ghép các cảnh bằng XFade (Chuyển cảnh nhẹ nhàng)
|
|
168
|
+
# Load per-scene transitions from storyboard.json
|
|
169
|
+
scene_transitions = load_storyboard_transitions(project_dir)
|
|
170
|
+
print("Bắt đầu Crossfade nối màn...")
|
|
171
|
+
merged_output = temp_dir / "merged_crossfaded.mp4"
|
|
172
|
+
fade_d = args.fade_duration
|
|
173
|
+
|
|
174
|
+
if len(ready_scenes) == 1:
|
|
175
|
+
merged_output = ready_scenes[0]
|
|
176
|
+
else:
|
|
177
|
+
# Tính toán duration để tính offset
|
|
178
|
+
durations = [get_duration(f) for f in ready_scenes]
|
|
179
|
+
|
|
180
|
+
filter_complex = ""
|
|
181
|
+
inputs = []
|
|
182
|
+
|
|
183
|
+
for i, sc in enumerate(ready_scenes):
|
|
184
|
+
inputs.extend(["-i", str(sc)])
|
|
185
|
+
|
|
186
|
+
offsets = []
|
|
187
|
+
current_offset = 0.0
|
|
188
|
+
|
|
189
|
+
for i in range(len(durations) - 1):
|
|
190
|
+
current_offset += durations[i] - fade_d
|
|
191
|
+
offsets.append(current_offset)
|
|
192
|
+
|
|
193
|
+
# Build video filter with per-scene transitions
|
|
194
|
+
v_labels = [f"[{i}:v]" for i in range(len(ready_scenes))]
|
|
195
|
+
for i in range(len(offsets)):
|
|
196
|
+
# Get transition for this join (from scene i to i+1)
|
|
197
|
+
transition_type = 'fade' # default
|
|
198
|
+
if i < len(scene_transitions):
|
|
199
|
+
t = scene_transitions[i]
|
|
200
|
+
if t != 'none':
|
|
201
|
+
transition_type = t
|
|
202
|
+
|
|
203
|
+
last_out = v_labels[0]
|
|
204
|
+
next_in = v_labels[i+1]
|
|
205
|
+
out_label = f"[v{i+1}]"
|
|
206
|
+
|
|
207
|
+
if i < len(scene_transitions) and scene_transitions[i] == 'none':
|
|
208
|
+
# No transition — simple concat (handled by offset = duration)
|
|
209
|
+
filter_complex += f"{last_out}{next_in}xfade=transition=fade:duration=0.01:offset={offsets[i]}{out_label}; "
|
|
210
|
+
else:
|
|
211
|
+
filter_complex += f"{last_out}{next_in}xfade=transition={transition_type}:duration={fade_d}:offset={offsets[i]}{out_label}; "
|
|
212
|
+
v_labels[0] = out_label # Cập nhật pipe hiện tại
|
|
213
|
+
|
|
214
|
+
print(f" Scene {i+1} → {i+2}: transition={transition_type}")
|
|
215
|
+
|
|
216
|
+
# Build audio filter
|
|
217
|
+
a_labels = [f"[{i}:a]" for i in range(len(ready_scenes))]
|
|
218
|
+
for i in range(len(offsets)):
|
|
219
|
+
last_out = a_labels[0]
|
|
220
|
+
next_in = a_labels[i+1]
|
|
221
|
+
out_label = f"[a{i+1}]"
|
|
222
|
+
filter_complex += f"{last_out}{next_in}acrossfade=d={fade_d}{out_label}; "
|
|
223
|
+
a_labels[0] = out_label
|
|
224
|
+
|
|
225
|
+
cmd_merge = ["ffmpeg", "-y"] + inputs + [
|
|
226
|
+
"-filter_complex", filter_complex.strip(" ;"),
|
|
227
|
+
"-map", v_labels[0],
|
|
228
|
+
"-map", a_labels[0],
|
|
229
|
+
"-c:v", "libx264", "-c:a", "aac", "-pix_fmt", "yuv420p",
|
|
230
|
+
str(merged_output)
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
print(" Đang render crossfade... (có thể mất vài phút)")
|
|
234
|
+
subprocess.run(cmd_merge, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
|
235
|
+
|
|
236
|
+
# Bước 2.5: Ghép hình nền tách phông xanh (nếu có)
|
|
237
|
+
if args.chroma_bg and os.path.exists(args.chroma_bg):
|
|
238
|
+
print(f"Bắt đầu tách phông xanh và ghép nền: {args.chroma_bg}...")
|
|
239
|
+
merged_chroma = temp_dir / "merged_chroma.mp4"
|
|
240
|
+
bg_ext = Path(args.chroma_bg).suffix.lower()
|
|
241
|
+
is_image = bg_ext in ['.png', '.jpg', '.jpeg']
|
|
242
|
+
|
|
243
|
+
filter_complex = (
|
|
244
|
+
f"[0:v][1:v]scale2ref=w=iw:h=ih[bg][ref];"
|
|
245
|
+
f"[ref]colorkey={args.chroma_color}:{args.chroma_sim}:{args.chroma_blend}[ckout];"
|
|
246
|
+
f"[bg][ckout]overlay=shortest=1"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if is_image:
|
|
250
|
+
cmd_chroma = [
|
|
251
|
+
"ffmpeg", "-y",
|
|
252
|
+
"-loop", "1", "-framerate", "30", "-i", str(args.chroma_bg),
|
|
253
|
+
"-i", str(merged_output),
|
|
254
|
+
"-filter_complex", filter_complex,
|
|
255
|
+
"-c:v", "libx264", "-c:a", "copy", "-pix_fmt", "yuv420p",
|
|
256
|
+
str(merged_chroma)
|
|
257
|
+
]
|
|
258
|
+
else:
|
|
259
|
+
cmd_chroma = [
|
|
260
|
+
"ffmpeg", "-y",
|
|
261
|
+
"-stream_loop", "-1", "-i", str(args.chroma_bg),
|
|
262
|
+
"-i", str(merged_output),
|
|
263
|
+
"-filter_complex", filter_complex,
|
|
264
|
+
"-c:v", "libx264", "-c:a", "copy", "-pix_fmt", "yuv420p",
|
|
265
|
+
str(merged_chroma)
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
subprocess.run(cmd_chroma, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
|
269
|
+
merged_output = merged_chroma
|
|
270
|
+
|
|
271
|
+
# Bước 3: Lồng nhạc nền
|
|
272
|
+
bgm_file = audio_dir / "bgm.mp3"
|
|
273
|
+
final_output = final_dir / "promo-final.mp4"
|
|
274
|
+
|
|
275
|
+
if bgm_file.exists():
|
|
276
|
+
print(f"Mix nhạc nền (âm lượng {args.bgm_volume})...")
|
|
277
|
+
cmd_bgm = [
|
|
278
|
+
"ffmpeg", "-y",
|
|
279
|
+
"-i", str(merged_output),
|
|
280
|
+
"-stream_loop", "-1", "-i", str(bgm_file),
|
|
281
|
+
"-filter_complex", f"[1:a]volume={args.bgm_volume}[bgm];[0:a][bgm]amix=inputs=2:duration=first:dropout_transition=2[a]",
|
|
282
|
+
"-map", "0:v:0", "-map", "[a]",
|
|
283
|
+
"-c:v", "copy", "-c:a", "aac",
|
|
284
|
+
str(final_output)
|
|
285
|
+
]
|
|
286
|
+
subprocess.run(cmd_bgm, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
|
287
|
+
else:
|
|
288
|
+
print("Không tìm thấy bgm.mp3, bỏ qua mix nhạc nền.")
|
|
289
|
+
import shutil
|
|
290
|
+
shutil.copy(merged_output, final_output)
|
|
291
|
+
|
|
292
|
+
print(f"\n✅ Hoàn tất! Video lưu tại: {final_output}")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
if __name__ == "__main__":
|
|
296
|
+
main()
|
|
Binary file
|
|
Binary file
|