mirror of
https://github.com/Hommy-master/capcut-mate.git
synced 2026-08-29 07:31:26 +08:00
Merge pull request #15 from Hommy-master/dev - 解决关键帧参数不兼容的问题。
解决关键帧参数不兼容的问题。
This commit is contained in:
+66
-1
@@ -55,6 +55,8 @@ POST /openapi/capcut-mate/v1/add_captions
|
||||
| transform_x | number | ❌ | 0.0 | X轴位置偏移(像素) |
|
||||
| transform_y | number | ❌ | 0.0 | Y轴位置偏移(像素) |
|
||||
| style_text | boolean | ❌ | false | 是否使用样式文本 |
|
||||
| has_shadow | boolean | ❌ | false | 是否启用文本阴影 |
|
||||
| shadow_info | object | ❌ | null | 文本阴影参数 |
|
||||
|
||||
### captions字段详细说明
|
||||
|
||||
@@ -128,6 +130,30 @@ captions是一个JSON字符串,包含字幕数组,每个字幕对象包含
|
||||
- 以画布中心为原点
|
||||
- 实际存储时会转换为半画布高单位(假设画布高度1080,即除以540)
|
||||
|
||||
#### 文本阴影参数
|
||||
|
||||
`shadow_info` 是一个对象,包含以下字段:
|
||||
|
||||
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| shadow_alpha | number | ❌ | 1.0 | 阴影不透明度,取值范围为[0, 1] |
|
||||
| shadow_color | string | ❌ | "#000000" | 阴影颜色(十六进制) |
|
||||
| shadow_diffuse | number | ❌ | 15.0 | 阴影扩散程度,取值范围为[0, 100] |
|
||||
| shadow_distance | number | ❌ | 5.0 | 阴影距离,取值范围为[0, 100] |
|
||||
| shadow_angle | number | ❌ | -45.0 | 阴影角度,取值范围为[-180, 180] |
|
||||
|
||||
当 `has_shadow` 设置为 `true` 但未提供 `shadow_info` 时,系统将使用以下默认阴影配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"shadow_color": "#000000",
|
||||
"shadow_alpha": 0.9,
|
||||
"shadow_diffuse": 15,
|
||||
"shadow_distance": 5,
|
||||
"shadow_angle": -45
|
||||
}
|
||||
```
|
||||
|
||||
## 响应格式
|
||||
|
||||
### 成功响应 (200)
|
||||
@@ -212,7 +238,7 @@ curl -X POST https://capcut-mate.jcaigc.cn/openapi/capcut-mate/v1/add_captions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"draft_url": "YOUR_DRAFT_URL",
|
||||
"captions": "[{\"start\":0,\"end\":5000000,\"text\":\"你好,剪映\",\"keyword\":\"好\",\"keyword_color\":\"#ff0000\"}]",
|
||||
"captions": "[{\"start\":0,\"end\":5000000,\"text\":\"你好,剪映\",\"keyword\":\"好\",\"keyword_color\":\"#ff0000\"]",
|
||||
"text_color": "#ffffff",
|
||||
"alignment": 1,
|
||||
"alpha": 1.0,
|
||||
@@ -224,6 +250,45 @@ curl -X POST https://capcut-mate.jcaigc.cn/openapi/capcut-mate/v1/add_captions \
|
||||
}'
|
||||
```
|
||||
|
||||
#### 4. 带文本阴影的字幕
|
||||
|
||||
```bash
|
||||
curl -X POST https://capcut-mate.jcaigc.cn/openapi/capcut-mate/v1/add_captions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"draft_url": "YOUR_DRAFT_URL",
|
||||
"captions": "[{\"start\":0,\"end\":5000000,\"text\":\"你好,剪映\"}]",
|
||||
"text_color": "#ffffff",
|
||||
"alignment": 1,
|
||||
"alpha": 1.0,
|
||||
"font_size": 20,
|
||||
"has_shadow": true,
|
||||
"shadow_info": {
|
||||
"shadow_alpha": 0.8,
|
||||
"shadow_color": "#000000",
|
||||
"shadow_diffuse": 20.0,
|
||||
"shadow_distance": 10.0,
|
||||
"shadow_angle": -45.0
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
#### 5. 使用默认文本阴影的字幕
|
||||
|
||||
```bash
|
||||
curl -X POST https://capcut-mate.jcaigc.cn/openapi/capcut-mate/v1/add_captions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"draft_url": "YOUR_DRAFT_URL",
|
||||
"captions": "[{\"start\":0,\"end\":5000000,\"text\":\"你好,剪映\"}]",
|
||||
"text_color": "#ffffff",
|
||||
"alignment": 1,
|
||||
"alpha": 1.0,
|
||||
"font_size": 20,
|
||||
"has_shadow": true
|
||||
}'
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 错误信息 | 说明 | 解决方案 |
|
||||
|
||||
+17
-6
@@ -19,7 +19,7 @@ POST /openapi/capcut-mate/v1/add_keyframes
|
||||
```json
|
||||
{
|
||||
"draft_url": "https://capcut-mate.jcaigc.cn/openapi/capcut-mate/v1/get_draft?draft_id=2025092811473036584258",
|
||||
"keyframes": "[{\"segment_id\":\"d62994b4-25fe-422a-a123-87ef05038558\",\"property\":\"KFTypePositionX\",\"offset\":0.5,\"value\":-0.1}]"
|
||||
"keyframes": "[{\"segment_id\":\"d62994b4-25fe-422a-a123-87ef05038558\",\"property\":\"KFTypePositionX\",\"offset\":5000000,\"value\":-0.1}]"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -39,9 +39,15 @@ keyframes 是一个JSON字符串,包含关键帧数组,每个关键帧对象
|
||||
|--------|------|------|------|
|
||||
| segment_id | string | ✅ | 目标片段的唯一标识ID |
|
||||
| property | string | ✅ | 动画属性类型,支持的类型见下表 |
|
||||
| offset | number | ✅ | 关键帧在片段中的时间偏移(0-1范围,0表示开始,1表示结束) |
|
||||
| offset | number | ✅ | 关键帧在片段中的时间偏移(微秒绝对时间) |
|
||||
| value | number | ✅ | 属性在该时间点的值 |
|
||||
|
||||
#### offset 参数说明
|
||||
|
||||
offset 参数只支持微秒绝对时间格式:
|
||||
- 以微秒为单位的整数,如 5000000 表示片段开始后5秒的位置
|
||||
- 系统会自动将微秒值转换为相对时间比例
|
||||
|
||||
#### 支持的动画属性类型
|
||||
|
||||
| 属性类型 | 描述 | 值范围 | 示例 |
|
||||
@@ -52,6 +58,11 @@ keyframes 是一个JSON字符串,包含关键帧数组,每个关键帧对象
|
||||
| KFTypeScaleY | Y轴缩放 | 0.1 到 10.0 | 1.0 (原始), 0.5 (缩小), 2.0 (放大) |
|
||||
| KFTypeRotation | 旋转角度 | -360 到 360 | 0 (无旋转), 90 (顺时针90度) |
|
||||
| KFTypeAlpha | 透明度 | 0.0 到 1.0 | 1.0 (不透明), 0.5 (半透明), 0.0 (透明) |
|
||||
| UNIFORM_SCALE | 统一缩放 | 0.1 到 10.0 | 1.0 (原始), 0.5 (缩小), 2.0 (放大) |
|
||||
| KFTypeSaturation | 饱和度 | -1.0 到 1.0 | 0.0 (原始), -0.5 (降低), 0.5 (增强) |
|
||||
| KFTypeContrast | 对比度 | -1.0 到 1.0 | 0.0 (原始), -0.5 (降低), 0.5 (增强) |
|
||||
| KFTypeBrightness | 亮度 | -1.0 到 1.0 | 0.0 (原始), -0.5 (变暗), 0.5 (变亮) |
|
||||
| KFTypeVolume | 音量 | 0.0 到 2.0 | 1.0 (原始), 0.5 (降低), 2.0 (增强) |
|
||||
|
||||
## 响应格式
|
||||
|
||||
@@ -85,14 +96,14 @@ keyframes 是一个JSON字符串,包含关键帧数组,每个关键帧对象
|
||||
|
||||
### cURL 示例
|
||||
|
||||
#### 1. 基本关键帧添加
|
||||
#### 1. 基本关键帧添加(使用微秒时间)
|
||||
|
||||
```bash
|
||||
curl -X POST https://capcut-mate.jcaigc.cn/openapi/capcut-mate/v1/add_keyframes \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"draft_url": "YOUR_DRAFT_URL",
|
||||
"keyframes": "[{\"segment_id\":\"d62994b4-25fe-422a-a123-87ef05038558\",\"property\":\"KFTypePositionX\",\"offset\":0,\"value\":0},{\"segment_id\":\"d62994b4-25fe-422a-a123-87ef05038558\",\"property\":\"KFTypePositionX\",\"offset\":1,\"value\":-0.5}]"
|
||||
"keyframes": "[{\"segment_id\":\"segment-id\",\"property\":\"UNIFORM_SCALE\",\"offset\":0,\"value\":1},{\"segment_id\":\"segment-id\",\"property\":\"UNIFORM_SCALE\",\"offset\":5000000,\"value\":1.3}]"
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -103,7 +114,7 @@ curl -X POST https://capcut-mate.jcaigc.cn/openapi/capcut-mate/v1/add_keyframes
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"draft_url": "YOUR_DRAFT_URL",
|
||||
"keyframes": "[{\"segment_id\":\"segment-uuid\",\"property\":\"KFTypePositionX\",\"offset\":0,\"value\":0},{\"segment_id\":\"segment-uuid\",\"property\":\"KFTypePositionY\",\"offset\":0,\"value\":0},{\"segment_id\":\"segment-uuid\",\"property\":\"KFTypeRotation\",\"offset\":0.5,\"value\":90},{\"segment_id\":\"segment-uuid\",\"property\":\"KFTypeAlpha\",\"offset\":1,\"value\":0}]"
|
||||
"keyframes": "[{\"segment_id\":\"segment-uuid\",\"property\":\"KFTypePositionX\",\"offset\":0,\"value\":0},{\"segment_id\":\"segment-uuid\",\"property\":\"KFTypePositionY\",\"offset\":0,\"value\":0},{\"segment_id\":\"segment-uuid\",\"property\":\"KFTypeRotation\",\"offset\":2500000,\"value\":90},{\"segment_id\":\"segment-uuid\",\"property\":\"KFTypeAlpha\",\"offset\":5000000,\"value\":0}]"
|
||||
}'
|
||||
```
|
||||
|
||||
@@ -124,7 +135,7 @@ curl -X POST https://capcut-mate.jcaigc.cn/openapi/capcut-mate/v1/add_keyframes
|
||||
|
||||
1. **片段ID验证**: segment_id 必须是草稿中存在的有效片段ID
|
||||
2. **片段类型限制**: 只有视觉片段(视频、图片、贴纸、文本)支持关键帧
|
||||
3. **时间偏移范围**: offset 值必须在 0.0-1.0 范围内
|
||||
3. **时间偏移范围**: offset 值必须是非负整数(微秒)
|
||||
4. **属性值范围**: 不同的属性类型有不同的值范围限制
|
||||
5. **重复关键帧**: 相同片段相同属性的关键帧会被累加,不会覆盖
|
||||
6. **性能考虑**: 单次请求建议不超过100个关键帧
|
||||
|
||||
+3
-1
@@ -208,7 +208,9 @@ def add_captions(acr: AddCaptionsRequest) -> AddCaptionsResponse:
|
||||
style_text=acr.style_text,
|
||||
underline=acr.underline,
|
||||
italic=acr.italic,
|
||||
bold=acr.bold
|
||||
bold=acr.bold,
|
||||
has_shadow=acr.has_shadow,
|
||||
shadow_info=acr.shadow_info
|
||||
)
|
||||
|
||||
return AddCaptionsResponse(
|
||||
|
||||
@@ -2,6 +2,15 @@ from pydantic import BaseModel, Field
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class ShadowInfo(BaseModel):
|
||||
"""文本阴影参数"""
|
||||
shadow_alpha: float = Field(default=1.0, ge=0.0, le=1.0, description="阴影不透明度, 取值范围为[0, 1]")
|
||||
shadow_color: str = Field(default="#000000", description="阴影颜色(十六进制)")
|
||||
shadow_diffuse: float = Field(default=15.0, ge=0.0, le=100.0, description="阴影扩散程度, 取值范围为[0, 100]")
|
||||
shadow_distance: float = Field(default=5.0, ge=0.0, le=100.0, description="阴影距离, 取值范围为[0, 100]")
|
||||
shadow_angle: float = Field(default=-45.0, ge=-180.0, le=180.0, description="阴影角度, 取值范围为[-180, 180]")
|
||||
|
||||
|
||||
class AddCaptionsRequest(BaseModel):
|
||||
"""批量添加字幕请求参数"""
|
||||
draft_url: str = Field(default="", description="草稿URL")
|
||||
@@ -22,6 +31,8 @@ class AddCaptionsRequest(BaseModel):
|
||||
underline: bool = Field(default=False, description="文字下划线开关")
|
||||
italic: bool = Field(default=False, description="文本斜体开关")
|
||||
bold: bool = Field(default=False, description="文本加粗开关")
|
||||
has_shadow: bool = Field(default=False, description="是否启用文本阴影")
|
||||
shadow_info: Optional[ShadowInfo] = Field(default=None, description="文本阴影参数")
|
||||
|
||||
|
||||
class CaptionItem(BaseModel):
|
||||
|
||||
+45
-10
@@ -2,10 +2,11 @@ import json
|
||||
from typing import List, Dict, Any, Tuple, Optional, Literal
|
||||
|
||||
from src.utils.logger import logger
|
||||
from src.pyJianYingDraft import ScriptFile, TrackType, TextSegment, TextStyle, ClipSettings, Timerange, FontType, TextBorder
|
||||
from src.pyJianYingDraft import ScriptFile, TrackType, TextSegment, TextStyle, ClipSettings, Timerange, FontType, TextBorder, TextShadow
|
||||
from src.utils.draft_cache import DRAFT_CACHE
|
||||
from exceptions import CustomException, CustomError
|
||||
from src.utils import helper
|
||||
from src.schemas.add_captions import ShadowInfo
|
||||
|
||||
|
||||
def add_captions(
|
||||
@@ -26,7 +27,9 @@ def add_captions(
|
||||
style_text: bool = False,
|
||||
underline: bool = False,
|
||||
italic: bool = False,
|
||||
bold: bool = False
|
||||
bold: bool = False,
|
||||
has_shadow: bool = False,
|
||||
shadow_info: Optional[ShadowInfo] = None
|
||||
) -> Tuple[str, str, List[str], List[str], List[dict]]:
|
||||
"""
|
||||
批量添加字幕到剪映草稿的业务逻辑
|
||||
@@ -83,7 +86,7 @@ def add_captions(
|
||||
logger.debug(f"Function parameters - text_color: {text_color}, border_color: {border_color}, "
|
||||
f"alignment: {alignment}, alpha: {alpha}, font: {font}, font_size: {font_size}, "
|
||||
f"scale_x: {scale_x}, scale_y: {scale_y}, transform_x: {transform_x}, transform_y: {transform_y}, "
|
||||
f"style_text: {style_text}, underline: {underline}, italic: {italic}, bold: {bold}")
|
||||
f"style_text: {style_text}, underline: {underline}, italic: {italic}, bold: {bold}, has_shadow: {has_shadow}, shadow_info: {shadow_info}")
|
||||
|
||||
try:
|
||||
# 1. 提取草稿ID
|
||||
@@ -134,7 +137,9 @@ def add_captions(
|
||||
style_text=style_text,
|
||||
underline=underline,
|
||||
italic=italic,
|
||||
bold=bold
|
||||
bold=bold,
|
||||
has_shadow=has_shadow,
|
||||
shadow_info=shadow_info
|
||||
)
|
||||
segment_ids.append(segment_id)
|
||||
text_ids.append(text_id)
|
||||
@@ -188,7 +193,9 @@ def add_caption_to_draft(
|
||||
style_text: bool = False,
|
||||
underline: bool = False,
|
||||
italic: bool = False,
|
||||
bold: bool = False
|
||||
bold: bool = False,
|
||||
has_shadow: bool = False,
|
||||
shadow_info: Optional[ShadowInfo] = None
|
||||
) -> Tuple[str, str, dict]:
|
||||
"""
|
||||
向剪映草稿中添加单个字幕
|
||||
@@ -210,6 +217,8 @@ def add_caption_to_draft(
|
||||
in_animation_duration: 入场动画时长,可选
|
||||
out_animation_duration: 出场动画时长,可选
|
||||
loop_animation_duration: 循环动画时长,可选
|
||||
has_shadow: 是否启用文本阴影
|
||||
shadow_info: 文本阴影参数
|
||||
其他参数:字幕样式设置
|
||||
|
||||
Returns:
|
||||
@@ -283,20 +292,46 @@ def add_caption_to_draft(
|
||||
transform_y=transform_y / script.height # 转换为画布高度单位
|
||||
)
|
||||
|
||||
# 7. 创建文本片段
|
||||
# 7. 创建文本阴影(如果启用了阴影)
|
||||
text_shadow = None
|
||||
if has_shadow:
|
||||
# 如果启用了阴影但没有提供shadow_info,则使用默认值
|
||||
if shadow_info is None:
|
||||
# 创建默认的阴影配置
|
||||
shadow_rgb_color = hex_to_rgb("#000000")
|
||||
text_shadow = TextShadow(
|
||||
alpha=0.9,
|
||||
color=shadow_rgb_color,
|
||||
diffuse=15.0,
|
||||
distance=5.0,
|
||||
angle=-45.0
|
||||
)
|
||||
else:
|
||||
# 使用提供的shadow_info配置
|
||||
shadow_rgb_color = hex_to_rgb(shadow_info.shadow_color)
|
||||
text_shadow = TextShadow(
|
||||
alpha=shadow_info.shadow_alpha,
|
||||
color=shadow_rgb_color,
|
||||
diffuse=shadow_info.shadow_diffuse,
|
||||
distance=shadow_info.shadow_distance,
|
||||
angle=shadow_info.shadow_angle
|
||||
)
|
||||
|
||||
# 8. 创建文本片段
|
||||
text_segment = TextSegment(
|
||||
text=caption['text'],
|
||||
timerange=timerange,
|
||||
style=text_style,
|
||||
border=text_border, # 添加边框
|
||||
font=font_type, # 添加字体
|
||||
shadow=text_shadow, # 添加阴影
|
||||
clip_settings=clip_settings
|
||||
)
|
||||
|
||||
logger.info(f"Created text segment, material_id: {text_segment.material_id}")
|
||||
logger.info(f"Text segment details - start: {caption['start']}, duration: {caption_duration}, text: {caption['text'][:50]}")
|
||||
|
||||
# 8. 处理关键词高亮
|
||||
# 9. 处理关键词高亮
|
||||
if caption.get('keyword'):
|
||||
keyword_color = caption.get('keyword_color', '#ff7100') # 默认橙色
|
||||
keyword_rgb_color = hex_to_rgb(keyword_color)
|
||||
@@ -305,7 +340,7 @@ def add_caption_to_draft(
|
||||
apply_keyword_highlight(text_segment, caption['keyword'], keyword_rgb_color, keyword_font_size)
|
||||
logger.info(f"Applied keyword highlighting: {caption['keyword']} with color {keyword_color} and font size {keyword_font_size}")
|
||||
|
||||
# 9. TODO: 处理动画效果(需要导入相应的动画类型)
|
||||
# 10. TODO: 处理动画效果(需要导入相应的动画类型)
|
||||
if caption.get('in_animation'):
|
||||
logger.info(f"In animation specified but not implemented yet: {caption['in_animation']}")
|
||||
if caption.get('out_animation'):
|
||||
@@ -313,10 +348,10 @@ def add_caption_to_draft(
|
||||
if caption.get('loop_animation'):
|
||||
logger.info(f"Loop animation specified but not implemented yet: {caption['loop_animation']}")
|
||||
|
||||
# 10. 向指定轨道添加片段
|
||||
# 11. 向指定轨道添加片段
|
||||
script.add_segment(text_segment, track_name)
|
||||
|
||||
# 11. 构造片段信息
|
||||
# 12. 构造片段信息
|
||||
segment_info = {
|
||||
"id": text_segment.segment_id,
|
||||
"start": caption['start'],
|
||||
|
||||
@@ -70,7 +70,7 @@ def add_keyframes(
|
||||
if segment is None:
|
||||
logger.error(f"Segment not found: {keyframe_item['segment_id']}, skipping this keyframe")
|
||||
failed_keyframes += 1
|
||||
continue # 继续处理下一个关键帧,而不是抛出异常
|
||||
continue # 继续处理下一个关键帧
|
||||
|
||||
# 验证片段类型
|
||||
if not isinstance(segment, VisualSegment):
|
||||
@@ -86,9 +86,18 @@ def add_keyframes(
|
||||
failed_keyframes += 1
|
||||
continue # 继续处理下一个关键帧
|
||||
|
||||
# 计算时间偏移(将相对位置转换为微秒)
|
||||
# 处理offset值:只支持微秒绝对时间,需要转换为相对时间
|
||||
segment_duration = segment.duration
|
||||
time_offset = int(keyframe_item['offset'] * segment_duration)
|
||||
offset_value = keyframe_item['offset']
|
||||
|
||||
# 将微秒绝对时间转换为相对时间(0-1范围)
|
||||
relative_offset = offset_value / segment_duration
|
||||
# 确保相对时间在有效范围内
|
||||
relative_offset = max(0.0, min(1.0, relative_offset))
|
||||
logger.info(f"Converting absolute offset {offset_value} microseconds to relative offset {relative_offset} for segment duration {segment_duration}")
|
||||
|
||||
# 计算时间偏移(将相对位置转换为微秒)
|
||||
time_offset = int(relative_offset * segment_duration)
|
||||
|
||||
logger.info(f"Adding keyframe to segment {keyframe_item['segment_id']}: property={property_enum.value}, time_offset={time_offset}, value={keyframe_item['value']}")
|
||||
|
||||
@@ -156,7 +165,7 @@ def parse_keyframes_data(json_str: str) -> List[Dict[str, Any]]:
|
||||
{
|
||||
"segment_id": "d62994b4-25fe-422a-a123-87ef05038558", # [必选] 目标片段的唯一标识ID
|
||||
"property": "KFTypePositionX", # [必选] 动画属性类型
|
||||
"offset": 0.5, # [必选] 关键帧在片段中的时间偏移(0-1范围)
|
||||
"offset": 5000000, # [必选] 关键帧在片段中的时间偏移(微秒绝对时间)
|
||||
"value": -0.1 # [必选] 属性在该时间点的值
|
||||
}
|
||||
]
|
||||
@@ -184,7 +193,8 @@ def parse_keyframes_data(json_str: str) -> List[Dict[str, Any]]:
|
||||
# 支持的动画属性类型
|
||||
supported_properties = {
|
||||
"KFTypePositionX", "KFTypePositionY", "KFTypeScaleX",
|
||||
"KFTypeScaleY", "KFTypeRotation", "KFTypeAlpha"
|
||||
"KFTypeScaleY", "KFTypeRotation", "KFTypeAlpha", "UNIFORM_SCALE",
|
||||
"KFTypeSaturation", "KFTypeContrast", "KFTypeBrightness", "KFTypeVolume"
|
||||
}
|
||||
|
||||
for i, item in enumerate(data):
|
||||
@@ -205,10 +215,10 @@ def parse_keyframes_data(json_str: str) -> List[Dict[str, Any]]:
|
||||
logger.error(f"the {i}th item has unsupported property type: {item['property']}")
|
||||
raise CustomException(CustomError.INVALID_KEYFRAME_INFO, f"the {i}th item has unsupported property type: {item['property']}")
|
||||
|
||||
# 验证offset范围(0-1)
|
||||
if not isinstance(item["offset"], (int, float)) or item["offset"] < 0.0 or item["offset"] > 1.0:
|
||||
logger.error(f"the {i}th item has invalid offset value: {item['offset']}, must be between 0.0 and 1.0")
|
||||
raise CustomException(CustomError.INVALID_KEYFRAME_INFO, f"the {i}th item has invalid offset value: {item['offset']}")
|
||||
# 验证offset是数字类型且为非负数
|
||||
if not isinstance(item["offset"], (int, float)) or item["offset"] < 0:
|
||||
logger.error(f"the {i}th item has invalid offset type or value: {item['offset']}, must be a non-negative number")
|
||||
raise CustomException(CustomError.INVALID_KEYFRAME_INFO, f"the {i}th item has invalid offset type or value: {item['offset']}")
|
||||
|
||||
# 验证value是数字类型
|
||||
if not isinstance(item["value"], (int, float)):
|
||||
@@ -219,11 +229,11 @@ def parse_keyframes_data(json_str: str) -> List[Dict[str, Any]]:
|
||||
processed_item = {
|
||||
"segment_id": str(item["segment_id"]),
|
||||
"property": item["property"],
|
||||
"offset": float(item["offset"]),
|
||||
"offset": float(item["offset"]), # 保持原始微秒值,在主函数中转换为相对时间
|
||||
"value": float(item["value"])
|
||||
}
|
||||
|
||||
result.append(processed_item)
|
||||
|
||||
logger.info(f"Successfully parsed {len(result)} keyframe items")
|
||||
return result
|
||||
return result
|
||||
|
||||
+15
-15
@@ -154,10 +154,10 @@ def add_video_to_draft(
|
||||
# 0. 下载视频
|
||||
video_path = download(url=video['video_url'], save_dir=draft_video_dir)
|
||||
|
||||
# 1. 使用指定的duration或计算值
|
||||
specified_duration = video.get('duration', video['end'] - video['start'])
|
||||
# 1. 获取视频播放时长(target duration)
|
||||
target_duration = video.get('duration', video['end'] - video['start'])
|
||||
|
||||
# 创建图像调节设置
|
||||
# 2. 创建图像调节设置
|
||||
clip_settings = draft.ClipSettings(
|
||||
alpha=alpha,
|
||||
scale_x=scale_x,
|
||||
@@ -166,25 +166,24 @@ def add_video_to_draft(
|
||||
transform_y=transform_y / video['height'] # 转换为半画布高单位
|
||||
)
|
||||
|
||||
# 计算在素材中的源时间范围
|
||||
source_duration = video['end'] - video['start']
|
||||
# 3. 计算在时间轴上的显示时长(source duration)
|
||||
display_duration = video['end'] - video['start']
|
||||
|
||||
# 创建视频素材
|
||||
# 4. 创建视频素材
|
||||
video_material = draft.VideoMaterial(video_path)
|
||||
|
||||
# 5. 创建视频片段
|
||||
video_segment = draft.VideoSegment(
|
||||
material=video_material,
|
||||
target_timerange=trange(start=video['start'], duration=source_duration),
|
||||
source_timerange=trange(start=0, duration=min(video_material.duration, source_duration)),
|
||||
target_timerange=trange(start=video['start'], duration=display_duration),
|
||||
source_timerange=trange(start=0, duration=min(video_material.duration, display_duration)),
|
||||
speed=1.0, # 保持原始速度
|
||||
volume=video['volume'],
|
||||
volume=video.get('volume', 1.0),
|
||||
clip_settings=clip_settings
|
||||
)
|
||||
|
||||
logger.info(f"material_id: {video_segment.material_instance.material_id}")
|
||||
logger.info(f"video_path: {video_path}, start: {video['start']}, duration: {source_duration}, volume: {video['volume']}")
|
||||
logger.info(f"video_path: {video_path}, start: {video['start']}, target_duration: {target_duration}, display_duration: {display_duration}, volume: {video.get('volume', 1.0)}")
|
||||
|
||||
# 2. 添加转场效果(如果指定了)
|
||||
# 6. 添加转场效果(如果指定了)
|
||||
transition_name = video.get('transition')
|
||||
if transition_name:
|
||||
transition_type = find_transition_type_by_name(transition_name)
|
||||
@@ -198,10 +197,10 @@ def add_video_to_draft(
|
||||
else:
|
||||
logger.warning(f"Transition type not found for name: {transition_name}")
|
||||
|
||||
# 3. 向指定轨道添加片段,
|
||||
# 7. 向指定轨道添加片段
|
||||
script.add_segment(video_segment, track_name)
|
||||
|
||||
# 修复:返回 segment_id 而不是 material_id
|
||||
# 8. 返回片段ID(注意:是segment_id而不是material_id)
|
||||
return video_segment.segment_id
|
||||
except CustomException:
|
||||
logger.info(f"Add video to draft failed, draft_video_dir: {draft_video_dir}, video: {video}")
|
||||
@@ -210,6 +209,7 @@ def add_video_to_draft(
|
||||
logger.error(f"Add video to draft failed, error: {str(e)}")
|
||||
raise CustomException(err=CustomError.VIDEO_ADD_FAILED)
|
||||
|
||||
|
||||
def find_transition_type_by_name(transition_name: str) -> Optional[draft.TransitionType]:
|
||||
"""
|
||||
根据转场名称查找对应的转场类型
|
||||
|
||||
Reference in New Issue
Block a user