mirror of
https://github.com/sun-guannan/CapCutAPI.git
synced 2025-11-25 03:15:00 +08:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eb6c8d761 | ||
|
|
b83a013a42 | ||
|
|
b74247e60c | ||
|
|
13a57ba0f2 | ||
|
|
ff61c38114 | ||
|
|
10ade1b57a | ||
|
|
e5e74e275a | ||
|
|
59c04e8f77 | ||
|
|
2cff5d2e20 | ||
|
|
aaf196f926 | ||
|
|
cfde6304f9 | ||
|
|
80c03c00ea | ||
|
|
b97931392c | ||
|
|
66fb7d066b | ||
|
|
4377d92548 | ||
|
|
77d2c9e108 | ||
|
|
d904c78187 | ||
|
|
04e01449e3 | ||
|
|
aba58dd845 | ||
|
|
83c33c15c4 | ||
|
|
f0ca6afe5a | ||
|
|
76a5bc3e15 | ||
|
|
d68eb4ddb7 | ||
|
|
2e82bb719c | ||
|
|
cc7cba9af6 | ||
|
|
fa16b9d310 | ||
|
|
0052c44e88 | ||
|
|
f45b2fe314 | ||
|
|
55c28c6b4a | ||
|
|
cce0500bc4 | ||
|
|
fc53398c27 | ||
|
|
29fc42bfc4 | ||
|
|
341fc022a9 | ||
|
|
195a927f04 | ||
|
|
0a05b6487b | ||
|
|
c94b11a1f5 | ||
|
|
8134ed9489 | ||
|
|
2dd2ff69bd | ||
|
|
4a77eea949 | ||
|
|
fef03abb47 |
@@ -3,7 +3,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
```
|
```
|
||||||
👏👏👏👏 庆祝github 700星,送出价值7000点不记名云渲染券:08B88A2C-1D16-4CE1-982E-E3732F2655F3
|
👏👏👏👏 庆祝github 800星,送出价值8000点不记名云渲染券:040346B5-8D8F-459E-8EE7-332C0B827117
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -246,6 +246,9 @@ mcp_client.call_tool("add_text", {
|
|||||||
|
|
||||||
调用 `save_draft` 会在`capcut_server.py`当前目录下生成一个 `dfd_` 开头的文件夹,将其复制到剪映/CapCut 草稿目录,即可在应用中看到生成的草稿。
|
调用 `save_draft` 会在`capcut_server.py`当前目录下生成一个 `dfd_` 开头的文件夹,将其复制到剪映/CapCut 草稿目录,即可在应用中看到生成的草稿。
|
||||||
|
|
||||||
|
## 模版
|
||||||
|
我们汇总了一些模版,放在`pattern`文件夹下。
|
||||||
|
|
||||||
## 社区与支持
|
## 社区与支持
|
||||||
|
|
||||||
我们欢迎各种形式的贡献!我们的迭代规则:
|
我们欢迎各种形式的贡献!我们的迭代规则:
|
||||||
@@ -256,7 +259,8 @@ mcp_client.call_tool("add_text", {
|
|||||||
|
|
||||||
|
|
||||||
## 进群交流
|
## 进群交流
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- 反馈问题
|
- 反馈问题
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
# Connect AI generates via CapCutAPI [Try it online](https://www.capcutapi.top)
|
# Connect AI generates via CapCutAPI [Try it online](https://www.capcutapi.top)
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
@@ -29,12 +30,15 @@ Enjoy It! 😀😀😀
|
|||||||
|
|
||||||
**Combine AI-generated images and videos using CapCutAPI**
|
**Combine AI-generated images and videos using CapCutAPI**
|
||||||
|
|
||||||
|
[More](pattern)
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=1zmQWt13Dx0)
|
[](https://www.youtube.com/watch?v=1zmQWt13Dx0)
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=IF1RDFGOtEU)
|
[](https://www.youtube.com/watch?v=IF1RDFGOtEU)
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=rGNLE_slAJ8)
|
[](https://www.youtube.com/watch?v=rGNLE_slAJ8)
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
@@ -156,7 +160,7 @@ response = requests.post("http://localhost:9001/add_text", json={
|
|||||||
"text": "Welcome to CapCutAPI",
|
"text": "Welcome to CapCutAPI",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"end": 5,
|
"end": 5,
|
||||||
"font": "Source Han Sans",
|
"font": "Source Han Sans",read
|
||||||
"font_color": "#FFD700",
|
"font_color": "#FFD700",
|
||||||
"font_size": 48,
|
"font_size": 48,
|
||||||
"shadow_enabled": True,
|
"shadow_enabled": True,
|
||||||
@@ -243,6 +247,10 @@ mcp_client.call_tool("add_text", {
|
|||||||
|
|
||||||
Calling `save_draft` will generate a folder starting with `dfd_` in the current directory of `capcut_server.py`. Copy this to the CapCut/Jianying drafts directory to see the generated draft in the application.
|
Calling `save_draft` will generate a folder starting with `dfd_` in the current directory of `capcut_server.py`. Copy this to the CapCut/Jianying drafts directory to see the generated draft in the application.
|
||||||
|
|
||||||
|
## Pattern
|
||||||
|
|
||||||
|
You can find a lot of pattern in the `pattern` directory.
|
||||||
|
|
||||||
## Community & Support
|
## Community & Support
|
||||||
|
|
||||||
We welcome contributions of all forms\! Our iteration rules are:
|
We welcome contributions of all forms\! Our iteration rules are:
|
||||||
@@ -253,20 +261,13 @@ We welcome contributions of all forms\! Our iteration rules are:
|
|||||||
|
|
||||||
## Contact Us
|
## Contact Us
|
||||||
|
|
||||||
If you want to:
|
|
||||||
|
|
||||||
- Feedback on issues
|
|
||||||
- Feature suggestions
|
|
||||||
- Get latest news
|
|
||||||
|
|
||||||
**Contact**: sguann2023@gmail.com
|
|
||||||
### 🤝 Collaboration
|
### 🤝 Collaboration
|
||||||
|
|
||||||
- **Video Production**: Want to use this API for batch production of videos? I offer free consulting services to help you use this API. In return, I'll ask for the production workflow template to be **open-sourced** in the `template` directory of this project.
|
- **Video Production**: Want to use this API for batch production of videos with AIGC?
|
||||||
|
|
||||||
- **Join us**: Our goal is to provide a stable and reliable video editing tool that integrates well with AI-generated images, videos, and audio. If you are interested, submit a PR and I'll see it. For more in-depth involvement, the code for the MCP Editing Agent, web-based editing client, and cloud rendering modules has not been open-sourced yet.
|
- **Join us**: Our goal is to provide a stable and reliable video editing tool that integrates well with AI-generated images, videos, and audio. If you are interested, submit a PR and I'll see it. For more in-depth involvement, the code for the MCP Editing Agent, web-based editing client, and cloud rendering modules has not been open-sourced yet.
|
||||||
|
|
||||||
**Contact**: sguann2023@gmail.com
|
**Contact**: abelchrisnic@gmail.com
|
||||||
|
|
||||||
## 📈 Star History
|
## 📈 Star History
|
||||||
|
|
||||||
@@ -280,6 +281,8 @@ If you want to:
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
[](https://mseep.ai/app/69c38d28-a97c-4397-849d-c3e3d241b800)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
*Made with ❤️ by the CapCutAPI Community*
|
*Made with ❤️ by the CapCutAPI Community*
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ def add_text_impl(
|
|||||||
draft_id: str | None = None, # Python 3.10+ 新语法
|
draft_id: str | None = None, # Python 3.10+ 新语法
|
||||||
transform_y: float = -0.8,
|
transform_y: float = -0.8,
|
||||||
transform_x: float = 0,
|
transform_x: float = 0,
|
||||||
font: str = "文轩体",
|
font: Optional[str] = None,
|
||||||
font_color: str = "#ffffff",
|
font_color: str = "#ffffff",
|
||||||
font_size: float = 8.0,
|
font_size: float = 8.0,
|
||||||
track_name: str = "text_main",
|
track_name: str = "text_main",
|
||||||
@@ -102,11 +102,14 @@ def add_text_impl(
|
|||||||
:return: Updated draft information
|
:return: Updated draft information
|
||||||
"""
|
"""
|
||||||
# Validate if font is in Font_type
|
# Validate if font is in Font_type
|
||||||
try:
|
if font is None:
|
||||||
font_type = getattr(Font_type, font)
|
font_type = None
|
||||||
except:
|
else:
|
||||||
available_fonts = [attr for attr in dir(Font_type) if not attr.startswith('_')]
|
try:
|
||||||
raise ValueError(f"Unsupported font: {font}, please use one of the fonts in Font_type: {available_fonts}")
|
font_type = getattr(Font_type, font)
|
||||||
|
except:
|
||||||
|
available_fonts = [attr for attr in dir(Font_type) if not attr.startswith('_')]
|
||||||
|
raise ValueError(f"Unsupported font: {font}, please use one of the fonts in Font_type: {available_fonts}")
|
||||||
|
|
||||||
# Validate alpha value range
|
# Validate alpha value range
|
||||||
if not 0.0 <= font_alpha <= 1.0:
|
if not 0.0 <= font_alpha <= 1.0:
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ def add_text_impl(text, start, end, font, font_color, font_size, track_name, dra
|
|||||||
|
|
||||||
return make_request("add_text", data)
|
return make_request("add_text", data)
|
||||||
|
|
||||||
def add_image_impl(image_url, width, height, start, end, track_name, draft_id=None,
|
def add_image_impl(image_url, start, end, width=None, height=None, track_name="image_main", draft_id=None,
|
||||||
transform_x=0, transform_y=0, scale_x=1.0, scale_y=1.0, transition=None, transition_duration=None,
|
transform_x=0, transform_y=0, scale_x=1.0, scale_y=1.0, transition=None, transition_duration=None,
|
||||||
mask_type=None, mask_center_x=0.0, mask_center_y=0.0, mask_size=0.5,
|
mask_type=None, mask_center_x=0.0, mask_center_y=0.0, mask_size=0.5,
|
||||||
mask_rotation=0.0, mask_feather=0.0, mask_invert=False,
|
mask_rotation=0.0, mask_feather=0.0, mask_invert=False,
|
||||||
|
|||||||
12
pattern/001-words-coze.md
Normal file
12
pattern/001-words-coze.md
Normal file
File diff suppressed because one or more lines are too long
626
pattern/001-words.py
Normal file
626
pattern/001-words.py
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from flask import Flask, request, jsonify, Response
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
sys.path.append('/Users/sunguannan/capcutapi')
|
||||||
|
from example import add_image_impl
|
||||||
|
|
||||||
|
PORT=9001 #端口
|
||||||
|
BASE_URL = f"http://localhost:{PORT}"
|
||||||
|
draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def make_request(endpoint, data, method='POST'):
|
||||||
|
"""Send HTTP request to the server and handle the response"""
|
||||||
|
url = f"{BASE_URL}/{endpoint}"
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
try:
|
||||||
|
if method == 'POST':
|
||||||
|
response = requests.post(url, data=json.dumps(data), headers=headers)
|
||||||
|
elif method == 'GET':
|
||||||
|
response = requests.get(url, params=data, headers=headers)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported HTTP method: {method}")
|
||||||
|
|
||||||
|
response.raise_for_status() # Raise an exception if the request fails
|
||||||
|
return response.json()
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Request error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print("Unable to parse server response")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def save_draft_impl(draft_id, draft_folder):
|
||||||
|
"""API wrapper for save_draft service"""
|
||||||
|
data = {
|
||||||
|
"draft_id": draft_id,
|
||||||
|
"draft_folder": draft_folder
|
||||||
|
}
|
||||||
|
return make_request("save_draft", data)
|
||||||
|
|
||||||
|
def query_script_impl(draft_id):
|
||||||
|
"""API wrapper for query_script service"""
|
||||||
|
data = {
|
||||||
|
"draft_id": draft_id
|
||||||
|
}
|
||||||
|
return make_request("query_script", data)
|
||||||
|
|
||||||
|
def add_text_impl(text, start, end, font, font_color, font_size, track_name, draft_folder="123", draft_id=None,
|
||||||
|
vertical=False, transform_x=0, transform_y=0, font_alpha=1.0,
|
||||||
|
border_color=None, border_width=0.0, border_alpha=1.0,
|
||||||
|
background_color=None, background_alpha=1.0, background_style=None,
|
||||||
|
background_round_radius=0.0, background_height=0.14, background_width=0.14,
|
||||||
|
background_horizontal_offset=0.5, background_vertical_offset=0.5,
|
||||||
|
shadow_enabled=False, shadow_alpha=0.9, shadow_angle=-45.0,
|
||||||
|
shadow_color="#000000", shadow_distance=5.0, shadow_smoothing=0.15,
|
||||||
|
bubble_effect_id=None, bubble_resource_id=None,
|
||||||
|
effect_effect_id=None,
|
||||||
|
intro_animation=None, intro_duration=0.5,
|
||||||
|
outro_animation=None, outro_duration=0.5,
|
||||||
|
width=1080, height=1920,
|
||||||
|
fixed_width=-1, fixed_height=-1,
|
||||||
|
text_styles=None):
|
||||||
|
"""Add text with support for multiple styles, shadows, and backgrounds"""
|
||||||
|
data = {
|
||||||
|
"draft_folder": draft_folder,
|
||||||
|
"text": text,
|
||||||
|
"start": start,
|
||||||
|
"end": end,
|
||||||
|
"font": font,
|
||||||
|
"font_color": font_color,
|
||||||
|
"font_size": font_size,
|
||||||
|
"alpha": font_alpha,
|
||||||
|
"track_name": track_name,
|
||||||
|
"vertical": vertical,
|
||||||
|
"transform_x": transform_x,
|
||||||
|
"transform_y": transform_y
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add border parameters
|
||||||
|
if border_color:
|
||||||
|
data["border_color"] = border_color
|
||||||
|
data["border_width"] = border_width
|
||||||
|
data["border_alpha"] = border_alpha
|
||||||
|
|
||||||
|
# Add background parameters
|
||||||
|
if background_color:
|
||||||
|
data["background_color"] = background_color
|
||||||
|
data["background_alpha"] = background_alpha
|
||||||
|
if background_style:
|
||||||
|
data["background_style"] = background_style
|
||||||
|
data["background_round_radius"] = background_round_radius
|
||||||
|
data["background_height"] = background_height
|
||||||
|
data["background_width"] = background_width
|
||||||
|
data["background_horizontal_offset"] = background_horizontal_offset
|
||||||
|
data["background_vertical_offset"] = background_vertical_offset
|
||||||
|
|
||||||
|
# Add shadow parameters
|
||||||
|
if shadow_enabled:
|
||||||
|
data["shadow_enabled"] = shadow_enabled
|
||||||
|
data["shadow_alpha"] = shadow_alpha
|
||||||
|
data["shadow_angle"] = shadow_angle
|
||||||
|
data["shadow_color"] = shadow_color
|
||||||
|
data["shadow_distance"] = shadow_distance
|
||||||
|
data["shadow_smoothing"] = shadow_smoothing
|
||||||
|
|
||||||
|
|
||||||
|
# Add bubble effect parameters
|
||||||
|
if bubble_effect_id:
|
||||||
|
data["bubble_effect_id"] = bubble_effect_id
|
||||||
|
if bubble_resource_id:
|
||||||
|
data["bubble_resource_id"] = bubble_resource_id
|
||||||
|
|
||||||
|
# Add text effect parameters
|
||||||
|
if effect_effect_id:
|
||||||
|
data["effect_effect_id"] = effect_effect_id
|
||||||
|
|
||||||
|
# Add intro animation parameters
|
||||||
|
if intro_animation:
|
||||||
|
data["intro_animation"] = intro_animation
|
||||||
|
data["intro_duration"] = intro_duration
|
||||||
|
|
||||||
|
# Add outro animation parameters
|
||||||
|
if outro_animation:
|
||||||
|
data["outro_animation"] = outro_animation
|
||||||
|
data["outro_duration"] = outro_duration
|
||||||
|
|
||||||
|
# Add size parameters
|
||||||
|
data["width"] = width
|
||||||
|
data["height"] = height
|
||||||
|
|
||||||
|
# Add fixed size parameters
|
||||||
|
if fixed_width > 0:
|
||||||
|
data["fixed_width"] = fixed_width
|
||||||
|
if fixed_height > 0:
|
||||||
|
data["fixed_height"] = fixed_height
|
||||||
|
|
||||||
|
if draft_id:
|
||||||
|
data["draft_id"] = draft_id
|
||||||
|
|
||||||
|
# Add text styles parameters
|
||||||
|
if text_styles:
|
||||||
|
data["text_styles"] = text_styles
|
||||||
|
|
||||||
|
if draft_id:
|
||||||
|
data["draft_id"] = draft_id
|
||||||
|
|
||||||
|
return make_request("add_text", data)
|
||||||
|
|
||||||
|
|
||||||
|
def group_sentences(corrected_srt, threshold=1.0):
|
||||||
|
"""按时间间隔分句"""
|
||||||
|
if not corrected_srt:
|
||||||
|
return []
|
||||||
|
sentences = []
|
||||||
|
current_sentence = [corrected_srt[0]]
|
||||||
|
for i in range(1, len(corrected_srt)):
|
||||||
|
prev_end = corrected_srt[i-1]["end"]
|
||||||
|
curr_start = corrected_srt[i]["start"]
|
||||||
|
if curr_start - prev_end > threshold:
|
||||||
|
sentences.append(current_sentence)
|
||||||
|
current_sentence = [corrected_srt[i]]
|
||||||
|
else:
|
||||||
|
current_sentence.append(corrected_srt[i])
|
||||||
|
sentences.append(current_sentence)
|
||||||
|
return sentences
|
||||||
|
|
||||||
|
|
||||||
|
def adjust_sentence_timing(sentences, gap_adjust=1, time_precision=3):
|
||||||
|
"""调整句子间的时间间隔,并保留原始时间"""
|
||||||
|
def round_time(t):
|
||||||
|
return round(t, time_precision) if time_precision is not None else t
|
||||||
|
|
||||||
|
adjusted_sentences = []
|
||||||
|
total_offset = 0.0
|
||||||
|
prev_end = sentences[0][-1]["end"]
|
||||||
|
|
||||||
|
# 第一句保持原时间
|
||||||
|
first_sentence = [
|
||||||
|
{
|
||||||
|
"word": w["word"],
|
||||||
|
"start": w["start"],
|
||||||
|
"end": w["end"],
|
||||||
|
"original_start": w["start"],
|
||||||
|
"original_end": w["end"]
|
||||||
|
}
|
||||||
|
for w in sentences[0]
|
||||||
|
]
|
||||||
|
adjusted_sentences.append(first_sentence)
|
||||||
|
|
||||||
|
for i in range(1, len(sentences)):
|
||||||
|
sentence = sentences[i]
|
||||||
|
curr_start = sentence[0]["start"]
|
||||||
|
natural_gap = curr_start - prev_end
|
||||||
|
adjusted_gap = natural_gap if gap_adjust == 0 else (1.0 if natural_gap > 1.0 else natural_gap)
|
||||||
|
move_amount = natural_gap - adjusted_gap
|
||||||
|
total_offset += move_amount
|
||||||
|
|
||||||
|
adjusted_sentence = []
|
||||||
|
for w in sentence:
|
||||||
|
adjusted_sentence.append({
|
||||||
|
"word": w["word"],
|
||||||
|
"start": round_time(w["start"] - total_offset),
|
||||||
|
"end": round_time(w["end"] - total_offset),
|
||||||
|
"original_start": w["start"],
|
||||||
|
"original_end": w["end"]
|
||||||
|
})
|
||||||
|
adjusted_sentences.append(adjusted_sentence)
|
||||||
|
prev_end = sentence[-1]["end"]
|
||||||
|
return adjusted_sentences
|
||||||
|
|
||||||
|
|
||||||
|
def split_into_paragraphs(sentence, max_words=5, max_chunk_duration=1.5):
|
||||||
|
"""把句子按词数和时长分段"""
|
||||||
|
paragraphs = []
|
||||||
|
i = 0
|
||||||
|
n = len(sentence)
|
||||||
|
while i < n:
|
||||||
|
paragraph = [sentence[i]]
|
||||||
|
current_start = sentence[i]["start"]
|
||||||
|
current_end = sentence[i]["end"]
|
||||||
|
i += 1
|
||||||
|
while i < n:
|
||||||
|
current_word = sentence[i]
|
||||||
|
is_continuous = abs(current_word["start"] - current_end) < 0.001
|
||||||
|
if (len(paragraph) >= max_words or
|
||||||
|
(current_word["end"] - current_start) >= max_chunk_duration or
|
||||||
|
not is_continuous):
|
||||||
|
break
|
||||||
|
paragraph.append(current_word)
|
||||||
|
current_end = current_word["end"]
|
||||||
|
i += 1
|
||||||
|
paragraphs.append(paragraph)
|
||||||
|
|
||||||
|
return paragraphs
|
||||||
|
|
||||||
|
|
||||||
|
def build_segments_by_mode(
|
||||||
|
mode,
|
||||||
|
paragraph,
|
||||||
|
track_name,
|
||||||
|
font,
|
||||||
|
font_size,
|
||||||
|
highlight_color,
|
||||||
|
normal_color,
|
||||||
|
transform_x,
|
||||||
|
transform_y,
|
||||||
|
fixed_width,
|
||||||
|
shadow_enabled,
|
||||||
|
shadow_color,
|
||||||
|
border_color,
|
||||||
|
border_width,
|
||||||
|
border_alpha,
|
||||||
|
background_color,
|
||||||
|
):
|
||||||
|
|
||||||
|
"""根据模式生成字幕片段"""
|
||||||
|
segments = []
|
||||||
|
#print("二级代码返回调试fx", fixed_width)
|
||||||
|
|
||||||
|
if mode == "word_pop":
|
||||||
|
# 单词跳出
|
||||||
|
for w in paragraph:
|
||||||
|
text_styles = []
|
||||||
|
word_count = len(w["word"].replace(" ", "")) #统计有多少个字
|
||||||
|
text_styles.append({
|
||||||
|
"start": 0,
|
||||||
|
"end": word_count,
|
||||||
|
"border": {
|
||||||
|
"alpha": border_alpha,
|
||||||
|
"color": border_color,
|
||||||
|
"width": border_width
|
||||||
|
}
|
||||||
|
})
|
||||||
|
segments.append({
|
||||||
|
"text": w["word"],
|
||||||
|
"start": w["start"],
|
||||||
|
"end": w["end"],
|
||||||
|
"font": font,
|
||||||
|
"track_name": track_name,
|
||||||
|
"font_color": normal_color,
|
||||||
|
"font_size": font_size,
|
||||||
|
"transform_x": transform_x,
|
||||||
|
"transform_y": transform_y,
|
||||||
|
"shadow_enabled": shadow_enabled,
|
||||||
|
"fixed_width": fixed_width,
|
||||||
|
"text_styles": text_styles,
|
||||||
|
|
||||||
|
"shadow_color": shadow_color,
|
||||||
|
"border_color": border_color,
|
||||||
|
"border_width": border_width,
|
||||||
|
"border_alpha": border_alpha,
|
||||||
|
|
||||||
|
"background_color": background_color,
|
||||||
|
})
|
||||||
|
|
||||||
|
elif mode == "word_highlight":
|
||||||
|
# 单词高亮:当前词亮,其他灰
|
||||||
|
paragraph_text = " ".join(w["word"] for w in paragraph)
|
||||||
|
offsets = []
|
||||||
|
ci = 0
|
||||||
|
for w in paragraph:
|
||||||
|
offsets.append((ci, ci + len(w["word"])))
|
||||||
|
ci += len(w["word"]) + 1
|
||||||
|
for idx, w in enumerate(paragraph):
|
||||||
|
text_styles = []
|
||||||
|
for k, (s, e) in enumerate(offsets):
|
||||||
|
color = highlight_color if k == idx else normal_color
|
||||||
|
text_styles.append({
|
||||||
|
"start": s,
|
||||||
|
"end": e,
|
||||||
|
"style": {
|
||||||
|
"color": color,
|
||||||
|
"size": font_size,
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"alpha": border_alpha,
|
||||||
|
"color": border_color,
|
||||||
|
"width": border_width
|
||||||
|
}
|
||||||
|
})
|
||||||
|
print("text_styles", text_styles)
|
||||||
|
|
||||||
|
segments.append({
|
||||||
|
"text": paragraph_text,
|
||||||
|
"start": w["start"],
|
||||||
|
"end": w["end"],
|
||||||
|
"font": font,
|
||||||
|
"track_name": track_name,
|
||||||
|
"font_color": normal_color,
|
||||||
|
"font_size": font_size,
|
||||||
|
"text_styles": text_styles,
|
||||||
|
"transform_x": transform_x,
|
||||||
|
"transform_y": transform_y,
|
||||||
|
"shadow_enabled": shadow_enabled,
|
||||||
|
"fixed_width": fixed_width,
|
||||||
|
|
||||||
|
|
||||||
|
"shadow_color": shadow_color,
|
||||||
|
"border_color": border_color,
|
||||||
|
"border_width": border_width,
|
||||||
|
"border_alpha": border_alpha,
|
||||||
|
|
||||||
|
"background_color": background_color,
|
||||||
|
})
|
||||||
|
|
||||||
|
elif mode == "sentence_fade":
|
||||||
|
# 句子渐显:已亮过的词继续保持亮
|
||||||
|
paragraph_text = " ".join(w["word"] for w in paragraph)
|
||||||
|
offsets = []
|
||||||
|
ci = 0
|
||||||
|
for w in paragraph:
|
||||||
|
offsets.append((ci, ci + len(w["word"])))
|
||||||
|
ci += len(w["word"]) + 1
|
||||||
|
for idx, w in enumerate(paragraph):
|
||||||
|
text_styles = []
|
||||||
|
for k, (s, e) in enumerate(offsets):
|
||||||
|
color = highlight_color if k <= idx else normal_color
|
||||||
|
text_styles.append({
|
||||||
|
"start": s,
|
||||||
|
"end": e,
|
||||||
|
"style": {"color": color, "size": font_size},
|
||||||
|
"border": {
|
||||||
|
"alpha": border_alpha,
|
||||||
|
"color": border_color,
|
||||||
|
"width": border_width
|
||||||
|
}
|
||||||
|
})
|
||||||
|
segments.append({
|
||||||
|
"text": paragraph_text,
|
||||||
|
"start": w["start"],
|
||||||
|
"end": w["end"],
|
||||||
|
"font": font,
|
||||||
|
"track_name": track_name,
|
||||||
|
"font_color": normal_color,
|
||||||
|
"font_size": font_size,
|
||||||
|
"text_styles": text_styles,
|
||||||
|
"transform_x": transform_x,
|
||||||
|
"transform_y": transform_y,
|
||||||
|
"shadow_enabled": shadow_enabled,
|
||||||
|
"fixed_width": fixed_width,
|
||||||
|
|
||||||
|
|
||||||
|
"shadow_color": shadow_color,
|
||||||
|
"border_color": border_color,
|
||||||
|
"border_width": border_width,
|
||||||
|
"border_alpha": border_alpha,
|
||||||
|
|
||||||
|
"background_color": background_color,
|
||||||
|
})
|
||||||
|
|
||||||
|
elif mode == "sentence_pop":
|
||||||
|
# 句子跳出
|
||||||
|
text = " ".join(w["word"] for w in paragraph)
|
||||||
|
start_time = paragraph[0]["start"]
|
||||||
|
end_time = paragraph[-1]["end"]
|
||||||
|
text_styles = []
|
||||||
|
word_count = len(text.replace(" ", "")) #统计有多少个字
|
||||||
|
text_styles.append({
|
||||||
|
"start": 0,
|
||||||
|
"end": word_count,
|
||||||
|
"border": {
|
||||||
|
"alpha": border_alpha,
|
||||||
|
"color": border_color,
|
||||||
|
"width": border_width
|
||||||
|
}
|
||||||
|
})
|
||||||
|
segments.append({
|
||||||
|
"text": text,
|
||||||
|
"start": start_time,
|
||||||
|
"end": end_time,
|
||||||
|
"font": font,
|
||||||
|
"track_name": track_name,
|
||||||
|
"font_color": normal_color,
|
||||||
|
"font_size": font_size,
|
||||||
|
"transform_x": transform_x,
|
||||||
|
"transform_y": transform_y,
|
||||||
|
"shadow_enabled": shadow_enabled,
|
||||||
|
"fixed_width": fixed_width,
|
||||||
|
"text_styles": text_styles,
|
||||||
|
|
||||||
|
|
||||||
|
"shadow_color": shadow_color,
|
||||||
|
"border_color": border_color,
|
||||||
|
"border_width": border_width,
|
||||||
|
"border_alpha": border_alpha,
|
||||||
|
|
||||||
|
"background_color": background_color,
|
||||||
|
})
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError(f"未知模式: {mode}")
|
||||||
|
"""segments.append({
|
||||||
|
"file_name": file_name,
|
||||||
|
})"""
|
||||||
|
|
||||||
|
return segments
|
||||||
|
|
||||||
|
corrected_srt = [{
|
||||||
|
"word": "Hello",
|
||||||
|
"start": 0.0,
|
||||||
|
"end": 0.64,
|
||||||
|
"confidence": 0.93917525
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "I'm",
|
||||||
|
"start": 0.64,
|
||||||
|
"end": 0.79999995,
|
||||||
|
"confidence": 0.9976464
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "PAWA",
|
||||||
|
"start": 0.79999995,
|
||||||
|
"end": 1.36,
|
||||||
|
"confidence": 0.6848311
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "Nice",
|
||||||
|
"start": 1.36,
|
||||||
|
"end": 1.52,
|
||||||
|
"confidence": 0.9850389
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "To",
|
||||||
|
"start": 1.52,
|
||||||
|
"end": 1.68,
|
||||||
|
"confidence": 0.9926886
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "Meet",
|
||||||
|
"start": 1.68,
|
||||||
|
"end": 2.08,
|
||||||
|
"confidence": 0.9972697
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "You",
|
||||||
|
"start": 2.08,
|
||||||
|
"end": 2.72,
|
||||||
|
"confidence": 0.9845563
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "Enjoy",
|
||||||
|
"start": 2.72,
|
||||||
|
"end": 3.04,
|
||||||
|
"confidence": 0.99794894
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "My",
|
||||||
|
"start": 3.04,
|
||||||
|
"end": 3.1999998,
|
||||||
|
"confidence": 0.9970203
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "Parttern",
|
||||||
|
"start": 3.1999998,
|
||||||
|
"end": 3.36,
|
||||||
|
"confidence": 0.9970235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "Thank",
|
||||||
|
"start": 3.36,
|
||||||
|
"end": 3.6799998,
|
||||||
|
"confidence": 0.98627764
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "You",
|
||||||
|
"start": 3.6799998,
|
||||||
|
"end": 4.0,
|
||||||
|
"confidence": 0.9939551
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def add_koubo_from_srt(
|
||||||
|
corrected_srt,
|
||||||
|
track_name,
|
||||||
|
mode="word_pop",
|
||||||
|
font="ZY_Modern",
|
||||||
|
font_size=32,
|
||||||
|
highlight_color="#FFD700",
|
||||||
|
normal_color="#AAAAAA", max_chunk_duration=1.5, max_words=5,
|
||||||
|
gap_adjust=1,
|
||||||
|
time_precision=3,
|
||||||
|
transform_x=0.5,
|
||||||
|
transform_y=0.3,
|
||||||
|
fixed_width=-1,
|
||||||
|
shadow_enabled=True,
|
||||||
|
shadow_color="#000000",
|
||||||
|
border_color="#000000",
|
||||||
|
border_width=0.5,
|
||||||
|
border_alpha=1.0,
|
||||||
|
background_color="#000000",
|
||||||
|
|
||||||
|
):
|
||||||
|
"""统一入口:根据 mode 选择字幕效果"""
|
||||||
|
sentences = group_sentences(corrected_srt)
|
||||||
|
adjusted_sentences = adjust_sentence_timing(sentences, gap_adjust, time_precision)
|
||||||
|
all_paragraphs = [split_into_paragraphs(s, max_words, max_chunk_duration) for s in adjusted_sentences]
|
||||||
|
|
||||||
|
draft_id_ret = None
|
||||||
|
for sentence_paragraphs in all_paragraphs:
|
||||||
|
for paragraph in sentence_paragraphs:
|
||||||
|
segments = build_segments_by_mode(
|
||||||
|
mode,
|
||||||
|
paragraph,
|
||||||
|
track_name,
|
||||||
|
font,
|
||||||
|
font_size,
|
||||||
|
highlight_color,
|
||||||
|
normal_color,
|
||||||
|
transform_x,
|
||||||
|
transform_y,
|
||||||
|
fixed_width,
|
||||||
|
shadow_enabled,
|
||||||
|
shadow_color,
|
||||||
|
border_color,
|
||||||
|
border_width,
|
||||||
|
border_alpha,
|
||||||
|
background_color,
|
||||||
|
|
||||||
|
)
|
||||||
|
#print("segments", segments)
|
||||||
|
|
||||||
|
for seg in segments:
|
||||||
|
#print("二级代码返回调试fx", seg)
|
||||||
|
if draft_id_ret:
|
||||||
|
seg["draft_id"] = draft_id_ret
|
||||||
|
print("seg", seg)
|
||||||
|
|
||||||
|
res = add_text_impl(**seg)
|
||||||
|
if draft_id_ret is None and isinstance(res, dict):
|
||||||
|
try:
|
||||||
|
draft_id_ret = res["output"]["draft_id"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return draft_id_ret
|
||||||
|
|
||||||
|
colors = {
|
||||||
|
"shadow_color": "#000000",
|
||||||
|
"border_color": "#FFD700",
|
||||||
|
"background_color": "#000000",
|
||||||
|
"normal_color": "#FFFFFF",
|
||||||
|
"highlight_color": "#DA70D6" # 紫色
|
||||||
|
}
|
||||||
|
|
||||||
|
draft_id = add_koubo_from_srt(
|
||||||
|
corrected_srt,
|
||||||
|
track_name="main_text",
|
||||||
|
font_size=15,
|
||||||
|
gap_adjust=0,
|
||||||
|
transform_x=0,
|
||||||
|
transform_y=-0.45,# 0=保持原间隔,1=调整>1s的间隔
|
||||||
|
fixed_width = 0.6,
|
||||||
|
mode="word_highlight",
|
||||||
|
shadow_enabled=True,
|
||||||
|
border_width=10,
|
||||||
|
border_alpha=1.0,
|
||||||
|
|
||||||
|
**colors,
|
||||||
|
|
||||||
|
font="ZY_Modern", #设置自己的字体,需要在字体库中添加
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
add_image_impl(image_url="https://pic1.imgdb.cn/item/689aff2758cb8da5c81e64a2.png", start = 0, end = 4, draft_id=draft_id)
|
||||||
|
|
||||||
|
save_result = save_draft_impl(draft_id, draft_folder)
|
||||||
|
|
||||||
|
print(save_result)
|
||||||
|
"""
|
||||||
|
# 单词高亮
|
||||||
|
mode="word_highlight"
|
||||||
|
# 单词跳出
|
||||||
|
mode="word_pop"
|
||||||
|
# 句子渐显
|
||||||
|
mode="sentence_fade"
|
||||||
|
# 句子跳出
|
||||||
|
mode="sentence_pop"
|
||||||
|
"""
|
||||||
606
pattern/002-relationship.py
Normal file
606
pattern/002-relationship.py
Normal file
@@ -0,0 +1,606 @@
|
|||||||
|
import json
|
||||||
|
import random
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Set API keys
|
||||||
|
QWEN_API_KEY = "your qwen api key"
|
||||||
|
PEXELS_API_KEY = "your pexels api key"
|
||||||
|
CAPCUT_API_KEY = "your capcut api key"
|
||||||
|
LICENSE_KEY="your capcut license key",
|
||||||
|
|
||||||
|
|
||||||
|
def llm(query = ""):
|
||||||
|
"""
|
||||||
|
Call the Tongyi Qianwen large language model
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary containing title and list of sentences
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Build system prompt
|
||||||
|
system_prompt = """
|
||||||
|
* **Context:** You are an AI expert specializing in modern interpersonal relationships and emotional communication. Your knowledge base is built on a deep understanding of popular emotional content on social media, and you excel at interpreting the dynamics and perspectives of relationships in a relaxed, colloquial style.
|
||||||
|
|
||||||
|
* **Objective:** When the user inputs "Give me some random returns", your goal is to **randomly create** advice about male-female emotions, behavioral habits, or relationship guidance. Your content must strictly mimic the unique style shown in the examples below.
|
||||||
|
|
||||||
|
* **Style:** Your generated content should have the following characteristics:
|
||||||
|
|
||||||
|
* **Structure:** Use a list format, with each point being a short, independent sentence.
|
||||||
|
* **Wording:** Use colloquial language, often using the "When..." sentence pattern to describe a scenario.
|
||||||
|
* **Theme:** Content should revolve around "how to understand the other person", "which behaviors are attractive", or "advice for a specific gender".
|
||||||
|
|
||||||
|
* **Tone:** Your tone should be friendly, sincere, slightly teasing, like a friend sharing experiences on social media.
|
||||||
|
|
||||||
|
* **Audience:** Your audience is anyone interested in modern emotional relationships who wants to get advice in a relaxed way.
|
||||||
|
|
||||||
|
* **Response:** When you receive the instruction "Give me some random returns", please **randomly select one** from the following examples as your response. Or, you can **randomly generate** a new one with a completely consistent style, and return it in the same JSON format:
|
||||||
|
|
||||||
|
**Example 1 (How to understand girls):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "How to understand girls",
|
||||||
|
"sentences": [
|
||||||
|
"Hands on her stomach (Insecure)",
|
||||||
|
"She leans on you (Feels safe)",
|
||||||
|
"Covers her smile (Thinks your going to judge)",
|
||||||
|
"Stops texting you (Feels like she is annoying you)",
|
||||||
|
"Says she is fine (She is everything but fine)",
|
||||||
|
"When she hugs you (You mean a lot to her)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 2 (Tips for girls):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Tips for the girls (From the guys)",
|
||||||
|
"sentences": [
|
||||||
|
"99/100 guys dont know what hip dips are and actually love your stretch marks",
|
||||||
|
"We can't tell that you like us by just viewing our story, just message us",
|
||||||
|
"When he's out with his boys, let him have this time (this is very important)",
|
||||||
|
"'I'm not ready for a relationship' - unless your the luckiest girl in the world your not getting cuffed",
|
||||||
|
"As Bruno mars said, 'your perfect just the way you are' so just be you, it'll work"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 3 (Things guys find attractive in girls):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Things girls do that guys find attractive",
|
||||||
|
"sentences": [
|
||||||
|
"Bed hair when it's all messy >>>",
|
||||||
|
"When you come work out with us",
|
||||||
|
"Your sleepy voice in the morning or after a nap",
|
||||||
|
"When you wear our t-shirts as pyjamas",
|
||||||
|
"When you have a funny or really bad laugh",
|
||||||
|
"When you initiate ...",
|
||||||
|
"When your good with animals or animals like you"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Build user prompt
|
||||||
|
user_prompt = f"Randomly create advice about male-female emotions, behavioral habits, or relationship guidance, based on user input: {query}"
|
||||||
|
|
||||||
|
# Prepare request data
|
||||||
|
url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {QWEN_API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"model": "qwen-plus",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": system_prompt
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": user_prompt
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 16384,
|
||||||
|
"response_format": {"type": "json_object"}
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Send HTTP request
|
||||||
|
response = requests.post(url, headers=headers, json=data, timeout=30)
|
||||||
|
|
||||||
|
# Check response status
|
||||||
|
if response.status_code == 200:
|
||||||
|
response_data = response.json()
|
||||||
|
|
||||||
|
# Extract content from response
|
||||||
|
if 'choices' in response_data and len(response_data['choices']) > 0:
|
||||||
|
content = response_data['choices'][0]['message']['content']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Parse JSON response
|
||||||
|
result = json.loads(content)
|
||||||
|
|
||||||
|
# Ensure result contains necessary fields
|
||||||
|
if "title" in result and "sentences" in result:
|
||||||
|
return result
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass # If JSON parsing fails, will return predefined example
|
||||||
|
|
||||||
|
# If API call fails or parsing fails, print error message
|
||||||
|
print(f"Error: {response.status_code}, {response.text if hasattr(response, 'text') else 'No response text'}")
|
||||||
|
except Exception as e:
|
||||||
|
# Catch all possible exceptions
|
||||||
|
print(f"Exception occurred: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def search_pexels_videos(query="twilight", min_duration=10, orientation="portrait", per_page=15):
|
||||||
|
"""
|
||||||
|
Call Pexels API to search for videos
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
query (str): Search keyword, default is "twilight"
|
||||||
|
min_duration (int): Minimum video duration (seconds), default is 10 seconds
|
||||||
|
orientation (str): Video orientation, default is "portrait"
|
||||||
|
per_page (int): Number of results per page, default is 15
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List containing video information
|
||||||
|
"""
|
||||||
|
url = "https://api.pexels.com/videos/search"
|
||||||
|
headers = {
|
||||||
|
"Authorization": PEXELS_API_KEY
|
||||||
|
}
|
||||||
|
params = {
|
||||||
|
"query": query,
|
||||||
|
"orientation": orientation,
|
||||||
|
"per_page": per_page,
|
||||||
|
"min_duration": min_duration
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers, params=params)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
videos = []
|
||||||
|
|
||||||
|
for video in data.get("videos", []):
|
||||||
|
# Get video file information
|
||||||
|
video_files = video.get("video_files", [])
|
||||||
|
# Filter out 16:9 ratio video files
|
||||||
|
portrait_videos = [file for file in video_files
|
||||||
|
if file.get("width") and file.get("height") and
|
||||||
|
file.get("height") / file.get("width") > 1.7] # Close to 16:9 ratio
|
||||||
|
|
||||||
|
if portrait_videos:
|
||||||
|
# Select highest quality video file
|
||||||
|
best_quality = max(portrait_videos, key=lambda x: x.get("width", 0) * x.get("height", 0))
|
||||||
|
|
||||||
|
videos.append({
|
||||||
|
"id": video.get("id"),
|
||||||
|
"url": video.get("url"),
|
||||||
|
"image": video.get("image"),
|
||||||
|
"duration": video.get("duration"),
|
||||||
|
"user": video.get("user", {}).get("name"),
|
||||||
|
"video_url": best_quality.get("link"),
|
||||||
|
"width": best_quality.get("width"),
|
||||||
|
"height": best_quality.get("height"),
|
||||||
|
"file_type": best_quality.get("file_type")
|
||||||
|
})
|
||||||
|
|
||||||
|
if videos:
|
||||||
|
random_video = random.choice(videos)
|
||||||
|
return random_video['video_url']
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
print(f"Error: {response.status_code}, {response.text}")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Exception occurred: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def create_capcut_draft(width=1080, height=1920):
|
||||||
|
"""
|
||||||
|
Call CapCut API to create a new draft
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
width (int): Video width, default is 1080
|
||||||
|
height (int): Video height, default is 1920
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary containing draft ID and download URL, or error message if failed
|
||||||
|
"""
|
||||||
|
url = "https://open.capcutapi.top/cut_jianying/create_draft"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {CAPCUT_API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"width": width,
|
||||||
|
"height": height
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"draft_id": result.get("output", {}).get("draft_id"),
|
||||||
|
"draft_url": result.get("output", {}).get("draft_url")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": result.get("error", "Unknown error")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP Error: {response.status_code}",
|
||||||
|
"response": response.text
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def add_video_to_draft(draft_id, video_url):
|
||||||
|
"""
|
||||||
|
Call CapCut API to add video to draft
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
draft_id (str): Draft ID
|
||||||
|
video_url (str): Video URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary containing draft ID and download URL, or error message if failed
|
||||||
|
"""
|
||||||
|
url = "https://open.capcutapi.top/cut_jianying/add_video"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {CAPCUT_API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"video_url": video_url,
|
||||||
|
"draft_id": draft_id,
|
||||||
|
"end": 10 # Set video duration to 10 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"draft_id": result.get("output", {}).get("draft_id"),
|
||||||
|
"draft_url": result.get("output", {}).get("draft_url")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": result.get("error", "Unknown error")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP Error: {response.status_code}",
|
||||||
|
"response": response.text
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_text_to_draft(draft_id, text, font="ZY_Starry",
|
||||||
|
font_color="#FFFFFF",
|
||||||
|
background_color="#000000",
|
||||||
|
background_alpha=0.5,
|
||||||
|
background_style=2,
|
||||||
|
background_round_radius=10,
|
||||||
|
transform_y=0,
|
||||||
|
transform_x=0,
|
||||||
|
font_size=10.0,
|
||||||
|
fixed_width=0.6,
|
||||||
|
track_name="text_main"):
|
||||||
|
"""
|
||||||
|
Call CapCut API to add text to draft
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
draft_id (str): Draft ID
|
||||||
|
text (str): Text content
|
||||||
|
start_time (float): Text start time on timeline (seconds), default is 0
|
||||||
|
end_time (float): Text end time on timeline (seconds), default is 5
|
||||||
|
font (str): Font, default is "ZY_Starry"
|
||||||
|
font_color (str): Font color, default is white
|
||||||
|
background_color (str): Background color, default is black
|
||||||
|
background_alpha (float): Background transparency, default is 0.5
|
||||||
|
transform_y (float): Y-axis position offset, default is 0
|
||||||
|
transform_x (float): X-axis position offset, default is 0
|
||||||
|
font_size (float): Font size, default is 10.0
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary containing draft ID and download URL, or error message if failed
|
||||||
|
"""
|
||||||
|
url = "https://open.capcutapi.top/cut_jianying/add_text"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {CAPCUT_API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"text": text,
|
||||||
|
"start": 0,
|
||||||
|
"end": 10,
|
||||||
|
"draft_id": draft_id,
|
||||||
|
"font": font,
|
||||||
|
"font_color": font_color,
|
||||||
|
"font_size": font_size,
|
||||||
|
"transform_y": transform_y,
|
||||||
|
"transform_x": transform_x,
|
||||||
|
"fixed_width": fixed_width,
|
||||||
|
"background_color": background_color,
|
||||||
|
"background_alpha": background_alpha,
|
||||||
|
"background_style": background_style,
|
||||||
|
"background_round_radius": background_round_radius,
|
||||||
|
"track_name": track_name
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"draft_id": result.get("output", {}).get("draft_id"),
|
||||||
|
"draft_url": result.get("output", {}).get("draft_url")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": result.get("error", "Unknown error")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP Error: {response.status_code}",
|
||||||
|
"response": response.text
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_video(draft_id, resolution="720P", framerate="24"):
|
||||||
|
"""
|
||||||
|
Call CapCut API to render video
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
draft_id (str): Draft ID
|
||||||
|
license_key (str): License key
|
||||||
|
resolution (str): Video resolution, default is "720P"
|
||||||
|
framerate (str): Video frame rate, default is "24"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary containing task ID, or error message if failed
|
||||||
|
"""
|
||||||
|
url = "https://open.capcutapi.top/cut_jianying/generate_video"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {CAPCUT_API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"draft_id": draft_id,
|
||||||
|
"license_key": LICENSE_KEY,
|
||||||
|
"resolution": resolution,
|
||||||
|
"framerate": framerate
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"task_id": result.get("output", {}).get("task_id")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": result.get("error", "Unknown error")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP Error: {response.status_code}",
|
||||||
|
"response": response.text
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
def check_task_status(task_id):
|
||||||
|
"""
|
||||||
|
Call CapCut API to check task status
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
task_id (str): Task ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary containing task status and results, or error message if failed
|
||||||
|
"""
|
||||||
|
url = "https://open.capcutapi.top/cut_jianying/task_status"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {CAPCUT_API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"task_id": task_id
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
output = result.get("output", {})
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"status": output.get("status"),
|
||||||
|
"progress": output.get("progress"),
|
||||||
|
"result": output.get("result"), # Video URL
|
||||||
|
"error": output.get("error")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": result.get("error", "Unknown error")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP Error: {response.status_code}",
|
||||||
|
"response": response.text
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
# Call LLM function and print result
|
||||||
|
result = llm()
|
||||||
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
# 1. Create draft
|
||||||
|
draft_result = create_capcut_draft()
|
||||||
|
print("Draft creation result:", json.dumps(draft_result, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
if draft_result.get("success"):
|
||||||
|
|
||||||
|
draft_id = draft_result.get("draft_id")
|
||||||
|
|
||||||
|
# 2. Search Pexels videos
|
||||||
|
video_url = search_pexels_videos()
|
||||||
|
print("Pexels video URL:", video_url)
|
||||||
|
|
||||||
|
# 3. Add video to draft
|
||||||
|
if video_url:
|
||||||
|
add_result = add_video_to_draft(draft_result.get("draft_id"), video_url)
|
||||||
|
print("Add video result:", json.dumps(add_result, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
|
||||||
|
# 5. Add title text
|
||||||
|
title_result = add_text_to_draft(
|
||||||
|
draft_id=draft_id,
|
||||||
|
text=result["title"],
|
||||||
|
font="ZY_Starry", # Use starry font
|
||||||
|
font_color="#FFFFFF", # White font
|
||||||
|
background_color="#000000", # Black background
|
||||||
|
background_alpha=1, # Background transparency
|
||||||
|
background_style=1,
|
||||||
|
background_round_radius=10,
|
||||||
|
transform_y=0.7, # Located at the top of the screen (1 is top edge, -1 is bottom edge)
|
||||||
|
transform_x=0, # Horizontally centered
|
||||||
|
font_size=13.0, # Larger font
|
||||||
|
track_name = "title",
|
||||||
|
fixed_width=0.6
|
||||||
|
)
|
||||||
|
print("Add title result:", json.dumps(title_result, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
# 6. Add sentence text
|
||||||
|
sentence_count = len(result["sentences"])
|
||||||
|
for i, sentence in enumerate(result["sentences"]):
|
||||||
|
# Calculate vertical position - evenly distributed in the middle of the screen
|
||||||
|
transform_y = 0.5 - (1.1 * (i + 1) / (sentence_count + 1))
|
||||||
|
|
||||||
|
# Determine horizontal alignment - odd sentences left-aligned, even sentences right-aligned
|
||||||
|
if i % 2 == 0: # Odd sentences (counting from 0)
|
||||||
|
transform_x = -0.5 # Left-aligned
|
||||||
|
else: # Even sentences
|
||||||
|
transform_x = 0.5 # Right-aligned
|
||||||
|
|
||||||
|
sentence_result = add_text_to_draft(
|
||||||
|
draft_id=draft_id,
|
||||||
|
text=sentence,
|
||||||
|
font="ZY_Fantasy", # Use fantasy font
|
||||||
|
font_color="#FFFFFF", # White font
|
||||||
|
transform_y=transform_y, # Vertical position
|
||||||
|
transform_x=transform_x, # Horizontal position (left-right alignment)
|
||||||
|
background_alpha=0,
|
||||||
|
font_size=7.0, # Smaller font
|
||||||
|
fixed_width=0.3,
|
||||||
|
track_name=f"text_{i}"
|
||||||
|
)
|
||||||
|
print(f"Add sentence {i+1} result:", json.dumps(sentence_result, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
# 7. Render video
|
||||||
|
print("\nStarting video rendering...")
|
||||||
|
generate_result = generate_video(draft_id)
|
||||||
|
print("Video rendering request result:", json.dumps(generate_result, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
if generate_result.get("success"):
|
||||||
|
task_id = generate_result.get("task_id")
|
||||||
|
print(f"Task ID: {task_id}, starting to poll task status...")
|
||||||
|
|
||||||
|
# 8. Poll task status
|
||||||
|
import time
|
||||||
|
max_attempts = 30 # Maximum 30 polling attempts
|
||||||
|
attempt = 0
|
||||||
|
|
||||||
|
while attempt < max_attempts:
|
||||||
|
status_result = check_task_status(task_id)
|
||||||
|
print(f"Poll count {attempt+1}, status:", json.dumps(status_result, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
if not status_result.get("success"):
|
||||||
|
print("Failed to check task status:", status_result.get("error"))
|
||||||
|
break
|
||||||
|
|
||||||
|
status = status_result.get("status")
|
||||||
|
if status == "SUCCESS":
|
||||||
|
print("\nVideo rendering successful!")
|
||||||
|
print("Video URL:", status_result.get("result"))
|
||||||
|
break
|
||||||
|
elif status == "FAILED":
|
||||||
|
print("\nVideo rendering failed:", status_result.get("error"))
|
||||||
|
break
|
||||||
|
|
||||||
|
# Wait 5 seconds before checking again
|
||||||
|
print("Waiting 5 seconds before checking again...")
|
||||||
|
time.sleep(5)
|
||||||
|
attempt += 1
|
||||||
|
|
||||||
|
if attempt >= max_attempts:
|
||||||
|
print("\nPolling timeout, please check task status manually later")
|
||||||
13
pattern/README.md
Normal file
13
pattern/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Pattern Gallery
|
||||||
|
|
||||||
|
## 001-words.py
|
||||||
|
|
||||||
|
[source](001-words.py)
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=HLSHaJuNtBw)
|
||||||
|
|
||||||
|
## 002-relationship.py
|
||||||
|
|
||||||
|
[source](002-relationship.py)
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=f2Q1OI_SQZo)
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
"name": "",
|
"name": "",
|
||||||
"new_version": "110.0.0",
|
"new_version": "110.0.0",
|
||||||
"relationships": [],
|
"relationships": [],
|
||||||
"render_index_track_mode_on": false,
|
"render_index_track_mode_on": true,
|
||||||
"retouch_cover": null,
|
"retouch_cover": null,
|
||||||
"source": "default",
|
"source": "default",
|
||||||
"static_cover_image_path": "",
|
"static_cover_image_path": "",
|
||||||
|
|||||||
Reference in New Issue
Block a user