mirror of
https://github.com/sun-guannan/CapCutAPI.git
synced 2025-11-25 03:15:00 +08:00
add text shadow feature
This commit is contained in:
@@ -5,7 +5,7 @@ from pyJianYingDraft import trange, Font_type
|
||||
from typing import Optional
|
||||
from pyJianYingDraft import exceptions
|
||||
from create_draft import get_or_create_draft
|
||||
from pyJianYingDraft.text_segment import TextBubble, TextEffect
|
||||
from pyJianYingDraft.text_segment import TextBubble, TextEffect, TextStyleRange
|
||||
|
||||
def add_text_impl(
|
||||
text: str,
|
||||
@@ -28,6 +28,18 @@ def add_text_impl(
|
||||
background_color: str = "#000000",
|
||||
background_style: int = 1,
|
||||
background_alpha: float = 0.0, # Default no background display
|
||||
background_round_radius: float = 0.0, # 背景圆角半径,范围0.0-1.0
|
||||
background_height: float = 0.14, # 背景高度,范围0.0-1.0
|
||||
background_width: float = 0.14, # 背景宽度,范围0.0-1.0
|
||||
background_horizontal_offset: float = 0.5, # 背景水平偏移,范围0.0-1.0
|
||||
background_vertical_offset: float = 0.5, # 背景垂直偏移,范围0.0-1.0
|
||||
# 阴影参数
|
||||
shadow_enabled: bool = False, # 是否启用阴影
|
||||
shadow_alpha: float = 0.9, # 阴影透明度,范围0.0-1.0
|
||||
shadow_angle: float = -45.0, # 阴影角度,范围-180.0-180.0
|
||||
shadow_color: str = "#000000", # 阴影颜色
|
||||
shadow_distance: float = 5.0, # 阴影距离
|
||||
shadow_smoothing: float = 0.15, # 阴影平滑度,范围0.0-1.0
|
||||
# Bubble effect
|
||||
bubble_effect_id: Optional[str] = None,
|
||||
bubble_resource_id: Optional[str] = None,
|
||||
@@ -41,6 +53,8 @@ def add_text_impl(
|
||||
height: int = 1920,
|
||||
fixed_width: float = -1, # Text fixed width ratio, default -1 means not fixed
|
||||
fixed_height: float = -1, # Text fixed height ratio, default -1 means not fixed
|
||||
# 多样式文本参数
|
||||
text_styles: Optional[List[TextStyleRange]] = None, # 文本的不同部分的样式列表
|
||||
):
|
||||
"""
|
||||
Add text subtitle to the specified draft (configurable parameter version)
|
||||
@@ -62,6 +76,17 @@ def add_text_impl(
|
||||
:param background_color: Background color (default black)
|
||||
:param background_style: Background style (default 1)
|
||||
:param background_alpha: Background transparency (default 0.0, no background display)
|
||||
:param background_round_radius: 背景圆角半径,范围0.0-1.0(默认0.0)
|
||||
:param background_height: 背景高度,范围0.0-1.0(默认0.14)
|
||||
:param background_width: 背景宽度,范围0.0-1.0(默认0.14)
|
||||
:param background_horizontal_offset: 背景水平偏移,范围0.0-1.0(默认0.5)
|
||||
:param background_vertical_offset: 背景垂直偏移,范围0.0-1.0(默认0.5)
|
||||
:param shadow_enabled: 是否启用阴影(默认False)
|
||||
:param shadow_alpha: 阴影透明度,范围0.0-1.0(默认0.9)
|
||||
:param shadow_angle: 阴影角度,范围-180.0-180.0(默认-45.0)
|
||||
:param shadow_color: 阴影颜色(默认黑色)
|
||||
:param shadow_distance: 阴影距离(默认5.0)
|
||||
:param shadow_smoothing: 阴影平滑度,范围0.0-1.0(默认0.15)
|
||||
:param bubble_effect_id: Bubble effect ID
|
||||
:param bubble_resource_id: Bubble resource ID
|
||||
:param effect_effect_id: Text effect ID
|
||||
@@ -73,6 +98,7 @@ def add_text_impl(
|
||||
:param height: Video height (pixels)
|
||||
:param fixed_width: Text fixed width ratio, range 0.0-1.0, default -1 means not fixed
|
||||
:param fixed_height: Text fixed height ratio, range 0.0-1.0, default -1 means not fixed
|
||||
:param text_styles: 文本的不同部分的样式列表,每个元素是一个TextStyleRange
|
||||
:return: Updated draft information
|
||||
"""
|
||||
# Validate if font is in Font_type
|
||||
@@ -130,7 +156,24 @@ def add_text_impl(
|
||||
text_background = draft.Text_background(
|
||||
color=background_color,
|
||||
style=background_style,
|
||||
alpha=background_alpha
|
||||
alpha=background_alpha,
|
||||
round_radius=background_round_radius,
|
||||
height=background_height,
|
||||
width=background_width,
|
||||
horizontal_offset=background_horizontal_offset,
|
||||
vertical_offset=background_vertical_offset
|
||||
)
|
||||
|
||||
# 创建text_shadow (阴影)
|
||||
text_shadow = None
|
||||
if shadow_enabled:
|
||||
text_shadow = draft.Text_shadow(
|
||||
has_shadow=shadow_enabled,
|
||||
alpha=shadow_alpha,
|
||||
angle=shadow_angle,
|
||||
color=shadow_color,
|
||||
distance=shadow_distance,
|
||||
smoothing=shadow_smoothing
|
||||
)
|
||||
|
||||
# Create bubble effect
|
||||
@@ -171,10 +214,21 @@ def add_text_impl(
|
||||
clip_settings=draft.Clip_settings(transform_y=transform_y, transform_x=transform_x),
|
||||
border=text_border,
|
||||
background=text_background,
|
||||
shadow=text_shadow,
|
||||
fixed_width=pixel_fixed_width,
|
||||
fixed_height=pixel_fixed_height
|
||||
)
|
||||
|
||||
# 应用多样式文本设置
|
||||
if text_styles:
|
||||
for style_range in text_styles:
|
||||
# 验证范围有效性
|
||||
if style_range.start < 0 or style_range.end > len(text) or style_range.start >= style_range.end:
|
||||
raise ValueError(f"无效的文本范围: [{style_range.start}, {style_range.end}), 文本长度: {len(text)}")
|
||||
|
||||
# 应用样式到特定文本范围
|
||||
text_segment.add_text_style(style_range)
|
||||
|
||||
if text_bubble:
|
||||
text_segment.add_bubble(text_bubble.effect_id, text_bubble.resource_id)
|
||||
if text_effect:
|
||||
|
||||
@@ -349,6 +349,19 @@ def add_text():
|
||||
background_color = data.get('background_color', "#000000")
|
||||
background_style = data.get('background_style', 0)
|
||||
background_alpha = data.get('background_alpha', 0.0)
|
||||
background_round_radius = data.get('background_round_radius', 0.0)
|
||||
background_height = data.get('background_height', 0.14) # 背景高度,范围0.0-1.0
|
||||
background_width = data.get('background_width', 0.14) # 背景宽度,范围0.0-1.0
|
||||
background_horizontal_offset = data.get('background_horizontal_offset', 0.5) # 背景水平偏移,范围0.0-1.0
|
||||
background_vertical_offset = data.get('background_vertical_offset', 0.5) # 背景垂直偏移,范围0.0-1.0
|
||||
|
||||
# 阴影参数
|
||||
shadow_enabled = data.get('shadow_enabled', False) # 是否启用阴影
|
||||
shadow_alpha = data.get('shadow_alpha', 0.9) # 阴影透明度,范围0.0-1.0
|
||||
shadow_angle = data.get('shadow_angle', -45.0) # 阴影角度,范围-180.0-180.0
|
||||
shadow_color = data.get('shadow_color', "#000000") # 阴影颜色
|
||||
shadow_distance = data.get('shadow_distance', 5.0) # 阴影距离
|
||||
shadow_smoothing = data.get('shadow_smoothing', 0.15) # 阴影平滑度,范围0.0-1.0
|
||||
|
||||
# Bubble and decorative text effects
|
||||
bubble_effect_id = data.get('bubble_effect_id')
|
||||
@@ -363,6 +376,50 @@ def add_text():
|
||||
outro_animation = data.get('outro_animation')
|
||||
outro_duration = data.get('outro_duration', 0.5)
|
||||
|
||||
# 新增多样式文本参数
|
||||
text_styles_data = data.get('text_styles', [])
|
||||
text_styles = None
|
||||
if text_styles_data:
|
||||
text_styles = []
|
||||
for style_data in text_styles_data:
|
||||
# 获取样式范围
|
||||
start_pos = style_data.get('start', 0)
|
||||
end_pos = style_data.get('end', 0)
|
||||
|
||||
# 创建文本样式
|
||||
style = Text_style(
|
||||
size=style_data.get('style',{}).get('size', font_size),
|
||||
bold=style_data.get('style',{}).get('bold', False),
|
||||
italic=style_data.get('style',{}).get('italic', False),
|
||||
underline=style_data.get('style',{}).get('underline', False),
|
||||
color=hex_to_rgb(style_data.get('style',{}).get('color', font_color)),
|
||||
alpha=style_data.get('style',{}).get('alpha', font_alpha),
|
||||
align=style_data.get('style',{}).get('align', 1),
|
||||
vertical=style_data.get('style',{}).get('vertical', vertical),
|
||||
letter_spacing=style_data.get('style',{}).get('letter_spacing', 0),
|
||||
line_spacing=style_data.get('style',{}).get('line_spacing', 0)
|
||||
)
|
||||
|
||||
# 创建描边(如果有)
|
||||
border = None
|
||||
if style_data.get('border',{}).get('width', 0) > 0:
|
||||
border = Text_border(
|
||||
alpha=style_data.get('border',{}).get('alpha', border_alpha),
|
||||
color=hex_to_rgb(style_data.get('border',{}).get('color', border_color)),
|
||||
width=style_data.get('border',{}).get('width', border_width)
|
||||
)
|
||||
|
||||
# 创建样式范围对象
|
||||
style_range = TextStyleRange(
|
||||
start=start_pos,
|
||||
end=end_pos,
|
||||
style=style,
|
||||
border=border,
|
||||
font_str=style_data.get('font', font)
|
||||
)
|
||||
|
||||
text_styles.append(style_range)
|
||||
|
||||
result = {
|
||||
"success": False,
|
||||
"output": "",
|
||||
@@ -397,6 +454,17 @@ def add_text():
|
||||
background_color=background_color,
|
||||
background_style=background_style,
|
||||
background_alpha=background_alpha,
|
||||
background_round_radius=background_round_radius,
|
||||
background_height=background_height,
|
||||
background_width=background_width,
|
||||
background_horizontal_offset=background_horizontal_offset,
|
||||
background_vertical_offset=background_vertical_offset,
|
||||
shadow_enabled=shadow_enabled,
|
||||
shadow_alpha=shadow_alpha,
|
||||
shadow_angle=shadow_angle,
|
||||
shadow_color=shadow_color,
|
||||
shadow_distance=shadow_distance,
|
||||
shadow_smoothing=shadow_smoothing,
|
||||
bubble_effect_id=bubble_effect_id,
|
||||
bubble_resource_id=bubble_resource_id,
|
||||
effect_effect_id=effect_effect_id,
|
||||
@@ -407,7 +475,8 @@ def add_text():
|
||||
width=width,
|
||||
height=height,
|
||||
fixed_width=fixed_width,
|
||||
fixed_height=fixed_height
|
||||
fixed_height=fixed_height,
|
||||
text_styles = text_styles
|
||||
)
|
||||
|
||||
result["success"] = True
|
||||
|
||||
@@ -63,6 +63,7 @@ def download_image(image_url, draft_name, material_name):
|
||||
# Use ffmpeg to download and convert image to PNG format
|
||||
command = [
|
||||
'ffmpeg',
|
||||
'-headers', 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36\r\nReferer: https://www.163.com/\r\n',
|
||||
'-i', image_url,
|
||||
'-vf', 'format=rgba', # Convert to RGBA format to support transparency
|
||||
'-frames:v', '1', # Ensure only one frame is processed
|
||||
@@ -150,7 +151,15 @@ def download_file(url:str, local_filename, max_retries=3, timeout=180):
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
print(f"Created directory: {directory}")
|
||||
|
||||
with requests.get(url, stream=True, timeout=timeout) as response:
|
||||
# Add headers
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
|
||||
'Referer': 'https://www.163.com/', # 网易的Referer
|
||||
'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
|
||||
}
|
||||
|
||||
with requests.get(url, stream=True, timeout=timeout, headers=headers) as response:
|
||||
response.raise_for_status()
|
||||
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
|
||||
489
example.py
489
example.py
@@ -2,15 +2,15 @@ import requests
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from settings.local import PORT
|
||||
from util import timing_decorator
|
||||
import functools
|
||||
import threading
|
||||
from pyJianYingDraft.text_segment import TextStyleRange, Text_style, Text_border
|
||||
|
||||
|
||||
# Base URL of the service, please modify according to actual situation
|
||||
BASE_URL = "http://localhost:9000"
|
||||
LICENSE_KEY = "539C3FEB-74AE48D4-A964D52B-C520F801" # Using trial version license key
|
||||
|
||||
BASE_URL = f"http://localhost:{PORT}"
|
||||
def make_request(endpoint, data, method='POST'):
|
||||
"""Send HTTP request to the server and handle the response"""
|
||||
url = f"{BASE_URL}/{endpoint}"
|
||||
@@ -37,7 +37,6 @@ def add_audio_track(audio_url, start, end, target_start, volume=1.0,
|
||||
speed=1.0, track_name="main_audio", effect_type=None, effect_params=None, draft_id=None):
|
||||
"""API call to add audio track"""
|
||||
data = {
|
||||
"license_key": LICENSE_KEY, # Using trial version license key
|
||||
"audio_url": audio_url,
|
||||
"start": start,
|
||||
"end": end,
|
||||
@@ -54,15 +53,23 @@ def add_audio_track(audio_url, start, end, target_start, volume=1.0,
|
||||
|
||||
return make_request("add_audio", 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.5, transform_y=0.5, font_alpha=1.0,
|
||||
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, outro_animation=None):
|
||||
"""API call to add text"""
|
||||
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"""
|
||||
data = {
|
||||
"license_key": LICENSE_KEY, # Using trial version license key
|
||||
"draft_folder": draft_folder,
|
||||
"text": text,
|
||||
"start": start,
|
||||
@@ -89,6 +96,21 @@ def add_text_impl(text, start, end, font, font_color, font_size, track_name,draf
|
||||
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:
|
||||
@@ -100,23 +122,45 @@ def add_text_impl(text, start, end, font, font_color, font_size, track_name,draf
|
||||
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
|
||||
|
||||
if outro_animation:
|
||||
data["outro_animation"] = outro_animation
|
||||
# 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 add_image_impl(image_url, width, height, start, end, track_name, draft_id=None,
|
||||
transform_x=0, transform_y=0, scale_x=1.0, scale_y=1.0, transition=None, transition_duration=None,
|
||||
# New mask-related parameters
|
||||
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_rect_width=None, mask_round_corner=None):
|
||||
mask_rect_width=None, mask_round_corner=None, background_blur=None):
|
||||
"""API call to add image"""
|
||||
data = {
|
||||
"license_key": LICENSE_KEY, # Using trial version license key
|
||||
"image_url": image_url,
|
||||
"width": width,
|
||||
"height": height,
|
||||
@@ -143,6 +187,8 @@ def add_image_impl(image_url, width, height, start, end, track_name, draft_id=No
|
||||
|
||||
if draft_id:
|
||||
data["draft_id"] = draft_id
|
||||
if background_blur:
|
||||
data["background_blur"] = background_blur
|
||||
|
||||
return make_request("add_image", data)
|
||||
|
||||
@@ -150,7 +196,6 @@ def generate_image_impl(prompt, width, height, start, end, track_name, draft_id=
|
||||
transform_x=0, transform_y=0, scale_x=1.0, scale_y=1.0, transition=None, transition_duration=None):
|
||||
"""API call to add image"""
|
||||
data = {
|
||||
"license_key": LICENSE_KEY, # Using trial version license key
|
||||
"prompt": prompt,
|
||||
"width": width,
|
||||
"height": height,
|
||||
@@ -176,7 +221,6 @@ def add_sticker_impl(resource_id, start, end, draft_id=None, transform_x=0, tran
|
||||
width=1080, height=1920):
|
||||
"""API call to add sticker"""
|
||||
data = {
|
||||
"license_key": LICENSE_KEY, # Using trial version license key
|
||||
"sticker_id": resource_id,
|
||||
"start": start,
|
||||
"end": end,
|
||||
@@ -208,7 +252,6 @@ def add_video_keyframe_impl(draft_id, track_name, property_type=None, time=None,
|
||||
2. Batch keyframes: using property_types, times, values parameters (in list form)
|
||||
"""
|
||||
data = {
|
||||
"license_key": LICENSE_KEY, # Using trial version license key
|
||||
"draft_id": draft_id,
|
||||
"track_name": track_name
|
||||
}
|
||||
@@ -237,10 +280,9 @@ def add_video_impl(video_url, start=None, end=None, width=None, height=None, tra
|
||||
# Mask-related parameters
|
||||
mask_type=None, mask_center_x=0.5, mask_center_y=0.5, mask_size=1.0,
|
||||
mask_rotation=0.0, mask_feather=0.0, mask_invert=False,
|
||||
mask_rect_width=None, mask_round_corner=None):
|
||||
mask_rect_width=None, mask_round_corner=None, background_blur=None):
|
||||
"""API call to add video track"""
|
||||
data = {
|
||||
"license_key": LICENSE_KEY, # Using trial version license key
|
||||
"video_url": video_url,
|
||||
"height": height,
|
||||
"track_name": track_name,
|
||||
@@ -272,13 +314,14 @@ def add_video_impl(video_url, start=None, end=None, width=None, height=None, tra
|
||||
data["width"] = width
|
||||
if height:
|
||||
data["height"] = height
|
||||
if background_blur:
|
||||
data["background_blur"] = background_blur
|
||||
return make_request("add_video", data)
|
||||
|
||||
def add_effect(effect_type, start, end, draft_id=None, track_name="effect_01",
|
||||
params=None, width=1080, height=1920):
|
||||
"""API call to add effect"""
|
||||
data = {
|
||||
"license_key": LICENSE_KEY, # Using trial version license key
|
||||
"effect_type": effect_type,
|
||||
"start": start,
|
||||
"end": end,
|
||||
@@ -381,6 +424,227 @@ def test_text():
|
||||
return result3
|
||||
|
||||
|
||||
def test_text_02():
|
||||
"""测试添加文本"""
|
||||
# draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||
draft_folder = "/Users/sunguannan/Movies/CapCut/User Data/Projects/com.lveditor.draft"
|
||||
|
||||
# 测试用例1:基本文本添加
|
||||
print("\n测试:添加基本文本")
|
||||
text_result = add_text_impl(
|
||||
text="你好,我是剪映助手",
|
||||
start=0,
|
||||
end=3,
|
||||
font="思源中宋",
|
||||
font_color="#FF0000", # 红色
|
||||
track_name="main_text",
|
||||
transform_y=0.8,
|
||||
transform_x=0.5,
|
||||
font_size=30.0
|
||||
)
|
||||
print("测试用例1(基本文本)成功:", text_result)
|
||||
|
||||
# 测试用例2:竖排文本
|
||||
result2 = add_text_impl(
|
||||
draft_id=text_result['output']['draft_id'],
|
||||
text="竖排文本演示",
|
||||
start=3,
|
||||
end=6,
|
||||
font="云书法三行魏碑体",
|
||||
font_color="#00FF00", # 绿色
|
||||
font_size=8.0,
|
||||
track_name="main_text",
|
||||
vertical=True, # 启用竖排
|
||||
transform_y=-0.5,
|
||||
outro_animation='晕开'
|
||||
)
|
||||
print("测试用例2(竖排文本)成功:", result2)
|
||||
|
||||
# 测试用例3:带描边和背景的文本
|
||||
result3 = add_text_impl(
|
||||
draft_id=result2['output']['draft_id'],
|
||||
text="描边和背景测试",
|
||||
start=6,
|
||||
end=9,
|
||||
font="思源中宋",
|
||||
font_color="#FFFFFF", # 白色文字
|
||||
font_size=24.0,
|
||||
track_name="main_text",
|
||||
transform_y=0.0,
|
||||
transform_x=0.5,
|
||||
border_color="#FF0000", # 红色描边
|
||||
border_width=20.0,
|
||||
border_alpha=1.0,
|
||||
background_color="#0000FF", # 蓝色背景
|
||||
background_alpha=0.5, # 半透明背景
|
||||
background_style=0 # 气泡样式背景
|
||||
)
|
||||
print("测试用例3(描边和背景)成功:", result3)
|
||||
|
||||
# 测试用例4:使用 TextStyleRange 的多样式文本
|
||||
# 创建不同的文本样式
|
||||
style1 = {
|
||||
"start": 0,
|
||||
"end": 2,
|
||||
"style": {
|
||||
"color": "#FF0000", # 红色
|
||||
"size": 30,
|
||||
"bold": True
|
||||
},
|
||||
"border": {
|
||||
"color": "#FFFFFF", # 白色描边
|
||||
"width": 40,
|
||||
"alpha": 1.0
|
||||
},
|
||||
"font": "思源中宋"
|
||||
}
|
||||
|
||||
style2 = {
|
||||
"start": 2,
|
||||
"end": 4,
|
||||
"style": {
|
||||
"color": "#00FF00", # 绿色
|
||||
"size": 25,
|
||||
"italic": True
|
||||
},
|
||||
"font": "挥墨体"
|
||||
}
|
||||
|
||||
style3 = {
|
||||
"start": 4,
|
||||
"end": 6,
|
||||
"style": {
|
||||
"color": "#0000FF", # 蓝色
|
||||
"size": 20,
|
||||
"underline": True
|
||||
},
|
||||
"font": "金陵体"
|
||||
}
|
||||
|
||||
# 添加多样式文本
|
||||
result4 = add_text_impl(
|
||||
draft_id=result3['output']['draft_id'],
|
||||
text="多样式\n文本测试",
|
||||
start=9,
|
||||
end=12,
|
||||
font="思源粗宋",
|
||||
track_name="main_text",
|
||||
transform_y=0.5,
|
||||
transform_x=0.5,
|
||||
font_color="#000000", # 默认黑色
|
||||
font_size=20.0,
|
||||
# 使用字典列表而不是 TextStyleRange 对象列表
|
||||
text_styles=[style1, style2, style3]
|
||||
)
|
||||
print("测试用例4(多样式文本)成功:", result4)
|
||||
|
||||
# 最后保存并上传草稿
|
||||
if result4.get('success') and result4.get('output'):
|
||||
save_result = save_draft_impl(result4['output']['draft_id'],draft_folder)
|
||||
print(f"草稿保存结果: {save_result}")
|
||||
|
||||
# 返回最后一个测试结果用于后续操作(如果有的话)
|
||||
return result4
|
||||
|
||||
|
||||
def test_text_03():
|
||||
"""测试添加文本"""
|
||||
draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||
# draft_folder = "/Users/sunguannan/Movies/CapCut/User Data/Projects/com.lveditor.draft"
|
||||
|
||||
# 测试用例1:基本文本添加
|
||||
print("\n测试:添加基本文本")
|
||||
text_result = add_text_impl(
|
||||
text="现在支持",
|
||||
start=0,
|
||||
end=6,
|
||||
font="挥墨体",
|
||||
font_color="#FFFFFF", # 红色
|
||||
track_name="text_01",
|
||||
transform_y=0.58,
|
||||
transform_x=0,
|
||||
font_size=24.0,
|
||||
intro_animation="弹入",
|
||||
intro_duration=0.5
|
||||
)
|
||||
print("测试用例1(基本文本)成功:", text_result)
|
||||
|
||||
# 测试用例2:带背景参数的文本
|
||||
result2 = add_text_impl(
|
||||
draft_id=text_result['output']['draft_id'],
|
||||
text="文字背景",
|
||||
start=1.5,
|
||||
end=6,
|
||||
font="思源中宋",
|
||||
font_color="#FFFFFF",
|
||||
font_size=20.0,
|
||||
track_name="text_2",
|
||||
transform_y=0.15,
|
||||
transform_x=0,
|
||||
background_color="#0000FF", # 蓝色背景
|
||||
background_alpha=0.7, # 70%透明度
|
||||
background_style=1,
|
||||
background_round_radius=0.5, # 圆角半径
|
||||
background_height=0.2, # 背景高度
|
||||
background_width=0.8, # 背景宽度
|
||||
background_horizontal_offset=0.5, # 水平居中
|
||||
background_vertical_offset=0.5, # 垂直居中
|
||||
intro_animation="弹入",
|
||||
intro_duration=0.5
|
||||
)
|
||||
print("测试用例2(背景参数)成功:", result2)
|
||||
|
||||
# 测试用例3:带阴影参数的文本
|
||||
result3 = add_text_impl(
|
||||
draft_id=result2['output']['draft_id'],
|
||||
text="文字阴影",
|
||||
start=3,
|
||||
end=6,
|
||||
font="金陵体",
|
||||
font_color="#FFFF00", # 黄色文字
|
||||
font_size=25.0,
|
||||
track_name="text3",
|
||||
transform_y=-0.16,
|
||||
transform_x=0,
|
||||
shadow_enabled=True, # 启用阴影
|
||||
shadow_alpha=0.8, # 阴影透明度
|
||||
shadow_angle=-45.0, # 阴影角度
|
||||
shadow_color="#0000FF", # 蓝色阴影
|
||||
shadow_distance=10.0, # 阴影距离
|
||||
shadow_smoothing=0.3, # 阴影平滑度
|
||||
intro_animation="弹入",
|
||||
intro_duration=0.5
|
||||
)
|
||||
print("测试用例3(阴影参数)成功:", result3)
|
||||
|
||||
# 测试用例4:带描边和背景的文本
|
||||
result4 = add_text_impl(
|
||||
draft_id=result3['output']['draft_id'],
|
||||
text="文字描边",
|
||||
start=4.5,
|
||||
end=6,
|
||||
font="思源中宋",
|
||||
font_color="#FFFFFF", # 白色文字
|
||||
font_size=24.0,
|
||||
track_name="text_4",
|
||||
transform_y=-0.58,
|
||||
border_color="#FF0000", # 红色描边
|
||||
border_width=20.0,
|
||||
border_alpha=1.0,
|
||||
intro_animation="弹入",
|
||||
intro_duration=0.5
|
||||
)
|
||||
print("测试用例4(综合参数)成功:", result4)
|
||||
|
||||
# 最后保存并上传草稿
|
||||
if text_result.get('success') and text_result.get('output'):
|
||||
save_result = save_draft_impl(text_result['output']['draft_id'],draft_folder)
|
||||
print(f"草稿保存结果: {save_result}")
|
||||
|
||||
# 返回最后一个测试结果用于后续操作(如果有的话)
|
||||
return text_result
|
||||
|
||||
|
||||
def test_image01():
|
||||
"""Test adding image"""
|
||||
# draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||
@@ -513,6 +777,23 @@ def test_image04():
|
||||
print(f"Image added successfully! {image_result['output']['draft_id']}")
|
||||
print(save_draft_impl(image_result['output']['draft_id'], draft_folder))
|
||||
|
||||
def test_image05():
|
||||
"""测试添加图片"""
|
||||
draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||
# draft_folder = "/Users/sunguannan/Movies/CapCut/User Data/Projects/com.lveditor.draft"
|
||||
|
||||
print("\n测试:添加图片1")
|
||||
image_result = add_image_impl(
|
||||
image_url="https://cdn.wanx.aliyuncs.com/wanx/1719234057367822001/text_to_image_v2/d6e33c84d7554146a25b1093b012838b_0.png?x-oss-process=image/resize,w_500/watermark,image_aW1nL3dhdGVyMjAyNDExMjkwLnBuZz94LW9zcy1wcm9jZXNzPWltYWdlL3Jlc2l6ZSxtX2ZpeGVkLHdfMTQ1LGhfMjU=,t_80,g_se,x_10,y_10/format,webp",
|
||||
width=1920,
|
||||
height=1080,
|
||||
start=5.0,
|
||||
end=10.0,
|
||||
track_name="image_main",
|
||||
background_blur=3
|
||||
)
|
||||
print(f"添加图片成功!{image_result['output']['draft_id']}")
|
||||
print(save_draft_impl(image_result['output']['draft_id'], draft_folder))
|
||||
|
||||
def test_mask_01():
|
||||
"""Test adding images to different tracks"""
|
||||
@@ -1369,6 +1650,28 @@ def test_video_track04():
|
||||
else:
|
||||
print("Unable to get draft ID, skipping save operation.")
|
||||
|
||||
def test_video_track05():
|
||||
"""测试添加视频轨道"""
|
||||
draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||
# draft_folder = "/Users/sunguannan/Movies/CapCut/User Data/Projects/com.lveditor.draft"
|
||||
|
||||
video_url = "https://cdn.wanx.aliyuncs.com/wanx/1719234057367822001/text_to_video/092faf3c94244973ab752ee1280ba76f.mp4?spm=5176.29623064.0.0.41ed26d6cBOhV3&file=092faf3c94244973ab752ee1280ba76f.mp4" # 替换为实际视频URL
|
||||
|
||||
print("\n测试:添加视频轨道")
|
||||
video_result = add_video_impl(
|
||||
video_url='https://p26-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/07bf6797a1834d75beb05c63293af204.mp4~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1782141919&x-signature=2ETX83Swh%2FwKzHeWB%2F9oGq9vqt4%3D&x-wf-file_name=output-997160b5.mp4',
|
||||
background_blur=2,
|
||||
width=1920,
|
||||
height=1080
|
||||
)
|
||||
|
||||
print(f"视频轨道添加结果: {video_result}")
|
||||
if video_result and 'output' in video_result and 'draft_id' in video_result['output']:
|
||||
draft_id = video_result['output']['draft_id']
|
||||
print(f"保存草稿: {save_draft_impl(draft_id, draft_folder)}")
|
||||
else:
|
||||
print("无法获取草稿ID,跳过保存操作。")
|
||||
|
||||
def test_keyframe():
|
||||
"""Test adding keyframes"""
|
||||
draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||
@@ -1837,152 +2140,6 @@ def test_transition_02():
|
||||
else:
|
||||
print("Unable to get draft ID, skipping save operation.")
|
||||
|
||||
def test_generate_image01():
|
||||
"""Test adding image"""
|
||||
draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||
|
||||
print("\nTest: Adding image 1")
|
||||
image_result = generate_image_impl(
|
||||
prompt="An Asian style doodle person floating in rough sea waves labeled 'Job Market', throwing paper boats made of resumes that are sinking, with a bank account notification bubble showing low balance. Atmosphere: Lost, anxious, turbulent. Art style: Minimalist line art, black and white cartoon style, bold outlines, extremely thick lines, expressive emotions, simple doodle, monochromatic. Composition: Wide angle showing person in center of chaotic elements. Lighting: Harsh contrast.",
|
||||
width=1024,
|
||||
height=1024,
|
||||
start=0,
|
||||
end=5.0,
|
||||
transform_y=0.7,
|
||||
scale_x=2.0,
|
||||
scale_y=1.0,
|
||||
transform_x=0,
|
||||
track_name="main"
|
||||
)
|
||||
print(f"Image generated successfully! {image_result['output']['draft_id']}")
|
||||
print(save_draft_impl(image_result['output']['draft_id'], draft_folder))
|
||||
|
||||
def generate_speech_impl(texts, draft_id=None, audio_track_name=None, language="Chinese",
|
||||
speaker_id="爽快思思/Skye",azure_speaker_id=None, speed_ratio=1.0, start_offset=0.0,
|
||||
end_padding=0.0, interval_time=0.5, volume=1.0, width=1080, height=1920,
|
||||
add_subtitle=True, text_track_name=None, font="文轩体",
|
||||
font_color="#ffffff", font_size=8.0, transform_y=-0.8, transform_x=0,
|
||||
vertical=False, font_alpha=1.0, border_alpha=1.0, border_color="#000000",
|
||||
border_width=0.0, background_color="#000000", background_style=1,
|
||||
background_alpha=0.0, 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):
|
||||
"""Generate TTS speech and add to draft API call"""
|
||||
data = {
|
||||
"license_key": LICENSE_KEY, # Using trial version license key
|
||||
"texts": texts,
|
||||
"audio_track_name": audio_track_name,
|
||||
"language": language,
|
||||
"speaker_id": speaker_id,
|
||||
"azure_speaker_id": azure_speaker_id,
|
||||
"speed_ratio": speed_ratio,
|
||||
"start_offset": start_offset,
|
||||
"end_padding": end_padding,
|
||||
"interval_time": interval_time,
|
||||
"volume": volume,
|
||||
"width": width,
|
||||
"height": height,
|
||||
"add_subtitle": add_subtitle,
|
||||
"text_track_name": text_track_name,
|
||||
"font": font,
|
||||
"font_color": font_color,
|
||||
"font_size": font_size,
|
||||
"transform_y": transform_y,
|
||||
"transform_x": transform_x,
|
||||
"vertical": vertical,
|
||||
"font_alpha": font_alpha,
|
||||
"border_alpha": border_alpha,
|
||||
"border_color": border_color,
|
||||
"border_width": border_width,
|
||||
"background_color": background_color,
|
||||
"background_style": background_style,
|
||||
"background_alpha": background_alpha,
|
||||
"bubble_effect_id": bubble_effect_id,
|
||||
"bubble_resource_id": bubble_resource_id,
|
||||
"effect_effect_id": effect_effect_id,
|
||||
"intro_animation": intro_animation,
|
||||
"intro_duration": intro_duration,
|
||||
"outro_animation": outro_animation,
|
||||
"outro_duration": outro_duration
|
||||
}
|
||||
|
||||
if draft_id:
|
||||
data["draft_id"] = draft_id
|
||||
|
||||
return make_request("generate_speech", data)
|
||||
|
||||
def test_generate_image02():
|
||||
"""Test adding image"""
|
||||
draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||
|
||||
print("\nTest: Adding image 1")
|
||||
image_result = generate_image_impl(
|
||||
prompt="A cat in the garden",
|
||||
width=1024,
|
||||
height=1024,
|
||||
start=0,
|
||||
end=5.0,
|
||||
transform_y=0.7,
|
||||
scale_x=2.0,
|
||||
scale_y=1.0,
|
||||
transform_x=0,
|
||||
track_name="main"
|
||||
)
|
||||
print("\nTest: Adding image 2")
|
||||
image_result = generate_image_impl(
|
||||
prompt="3 dogs running in the snow",
|
||||
draft_id=image_result['output']['draft_id'],
|
||||
width=576,
|
||||
height=1024,
|
||||
start=5.0,
|
||||
end=10.0,
|
||||
transform_y=-0.7,
|
||||
scale_x=2.0,
|
||||
scale_y=2.0,
|
||||
transform_x=0,
|
||||
track_name="main"
|
||||
)
|
||||
print(f"Image generated successfully! {image_result['output']['draft_id']}")
|
||||
print(save_draft_impl(image_result['output']['draft_id'], draft_folder))
|
||||
|
||||
@timing_decorator('TTS Speech Generation')
|
||||
def test_speech_01():
|
||||
"""Test TTS speech generation and subtitle addition"""
|
||||
draft_folder = "/Users/sunguannan/Movies/JianyingPro/User Data/Projects/com.lveditor.draft"
|
||||
|
||||
print("\nTest: Generate TTS speech and add subtitles")
|
||||
speech_result = generate_speech_impl(
|
||||
texts=["Hello everyone, welcome to my video", "Today we will discuss an interesting topic","What to do when your child doesn't want to go to school", "Hope you enjoy this content","Hello everyone, welcome to my video", "Today we will discuss an interesting topic","What to do when your child doesn't want to go to school", "Hope you enjoy this content","Hello everyone, welcome to my video", "Today we will discuss an interesting topic","What to do when your child doesn't want to go to school", "Hope you enjoy this content"],
|
||||
language="Chinese",
|
||||
draft_id="123",
|
||||
speaker_id="渊博小叔",
|
||||
azure_speaker_id="zh-CN-YunjianNeural",
|
||||
speed_ratio=1.0,
|
||||
start_offset=1.0,
|
||||
end_padding=1.0,
|
||||
interval_time=0.5,
|
||||
volume=0.8,
|
||||
width=1080,
|
||||
height=1920,
|
||||
add_subtitle=True,
|
||||
font="文轩体",
|
||||
font_color="#ffffff",
|
||||
font_size=8.0,
|
||||
transform_y=-0.8,
|
||||
transform_x=0,
|
||||
border_width=2.0,
|
||||
border_color="#000000",
|
||||
border_alpha=0.8
|
||||
)
|
||||
print(f"TTS speech generation result: {speech_result}")
|
||||
|
||||
# if speech_result.get('success'):
|
||||
# # Save draft
|
||||
# save_result = save_draft_impl(speech_result['output']['draft_id'], draft_folder)
|
||||
# print(f"Draft saving result: {save_result}")
|
||||
# else:
|
||||
# print(f"TTS generation failed: {speech_result.get('error')}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test01()
|
||||
# test02()
|
||||
|
||||
@@ -5,7 +5,7 @@ from .time_util import Timerange
|
||||
from .audio_segment import Audio_segment
|
||||
from .video_segment import Video_segment, Sticker_segment, Clip_settings
|
||||
from .effect_segment import Effect_segment, Filter_segment
|
||||
from .text_segment import Text_segment, Text_style, Text_border, Text_background
|
||||
from .text_segment import Text_segment, Text_style, Text_border, Text_background, Text_shadow
|
||||
|
||||
from .metadata import Font_type
|
||||
from .metadata import Mask_type
|
||||
@@ -72,6 +72,7 @@ __all__ = [
|
||||
"Text_style",
|
||||
"Text_border",
|
||||
"Text_background",
|
||||
"Text_shadow",
|
||||
"Track_type",
|
||||
"Shrink_mode",
|
||||
"Extend_mode",
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
import uuid
|
||||
from copy import deepcopy
|
||||
|
||||
from typing import Dict, Tuple, Any
|
||||
from typing import Dict, Tuple, Any, List
|
||||
from typing import Union, Optional, Literal
|
||||
|
||||
from pyJianYingDraft.metadata.capcut_text_animation_meta import CapCut_Text_intro, CapCut_Text_outro, CapCut_Text_loop_anim
|
||||
@@ -166,6 +166,51 @@ class Text_background:
|
||||
"background_vertical_offset": self.vertical_offset,
|
||||
}
|
||||
|
||||
class Text_shadow:
|
||||
"""文本阴影参数"""
|
||||
|
||||
has_shadow: bool
|
||||
"""是否启用阴影"""
|
||||
alpha: float
|
||||
"""阴影不透明度"""
|
||||
angle: float
|
||||
"""阴影角度"""
|
||||
color: str
|
||||
"""阴影颜色,格式为'#RRGGBB'"""
|
||||
distance: float
|
||||
"""阴影距离"""
|
||||
smoothing: float
|
||||
"""阴影平滑度"""
|
||||
|
||||
def __init__(self, *, has_shadow: bool = False, alpha: float = 0.9, angle: float = -45.0,
|
||||
color: str = "#000000", distance: float = 5.0, smoothing: float = 0.45):
|
||||
"""
|
||||
Args:
|
||||
has_shadow (`bool`, optional): 是否启用阴影,默认为False
|
||||
alpha (`float`, optional): 阴影不透明度,取值范围[0, 1],默认为0.9
|
||||
angle (`float`, optional): 阴影角度,取值范围[-180, 180], 默认为-45.0
|
||||
color (`str`, optional): 阴影颜色,格式为'#RRGGBB',默认为黑色
|
||||
distance (`float`, optional): 阴影距离,默认为5.0
|
||||
smoothing (`float`, optional): 阴影平滑度,取值范围[0, 1], 默认0.15
|
||||
"""
|
||||
self.has_shadow = has_shadow
|
||||
self.alpha = alpha
|
||||
self.angle = angle
|
||||
self.color = color
|
||||
self.distance = distance
|
||||
self.smoothing = smoothing
|
||||
|
||||
def export_json(self) -> Dict[str, Any]:
|
||||
"""生成子JSON数据,在Text_segment导出时合并到其中"""
|
||||
return {
|
||||
"has_shadow": self.has_shadow,
|
||||
"shadow_alpha": self.alpha,
|
||||
"shadow_angle": self.angle,
|
||||
"shadow_color": self.color,
|
||||
"shadow_distance": self.distance,
|
||||
"shadow_smoothing": self.smoothing * 3
|
||||
}
|
||||
|
||||
class TextBubble:
|
||||
"""文本气泡素材, 与滤镜素材本质上一致"""
|
||||
|
||||
@@ -200,6 +245,50 @@ class TextEffect(TextBubble):
|
||||
ret["source_platform"] = 1
|
||||
return ret
|
||||
|
||||
class TextStyleRange:
|
||||
"""文本样式范围类,用于定义文本特定范围的样式"""
|
||||
|
||||
start: int
|
||||
"""起始位置(包含)"""
|
||||
end: int
|
||||
"""结束位置(不包含)"""
|
||||
style: Text_style
|
||||
"""字体样式"""
|
||||
border: Optional[Text_border]
|
||||
"""文本描边参数,None表示无描边"""
|
||||
font: Optional[Effect_meta]
|
||||
"""字体设置,None表示使用全局字体"""
|
||||
|
||||
def __init__(self, start: int, end: int, style: Text_style, border: Optional[Text_border] = None, font_str:str = None):
|
||||
"""创建文本样式范围
|
||||
|
||||
Args:
|
||||
start (`int`): 起始位置(包含)
|
||||
end (`int`): 结束位置(不包含)
|
||||
style (`Text_style`): 字体样式
|
||||
border (`Text_border`, optional): 文本描边参数,默认为None(无描边)
|
||||
font (optional): 字体设置,默认为None(使用全局字体)
|
||||
"""
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.style = style
|
||||
self.border = border
|
||||
if font_str:
|
||||
try:
|
||||
font_type = getattr(Font_type, font_str).value
|
||||
except:
|
||||
available_fonts = [attr for attr in dir(Font_type) if not attr.startswith('_')]
|
||||
raise ValueError(f"不支持的字体:{font_str},请使用Font_type中的字体之一:{available_fonts}")
|
||||
self.font = font_type
|
||||
|
||||
def get_range(self) -> List[int]:
|
||||
"""获取范围列表
|
||||
|
||||
Returns:
|
||||
`List[int]`: [start, end] 形式的范围列表
|
||||
"""
|
||||
return [self.start, self.end]
|
||||
|
||||
class Text_segment(Visual_segment):
|
||||
"""文本片段类, 目前仅支持设置基本的字体样式"""
|
||||
|
||||
@@ -215,6 +304,9 @@ class Text_segment(Visual_segment):
|
||||
background: Optional[Text_background]
|
||||
"""文本背景参数, None表示无背景"""
|
||||
|
||||
shadow: Optional[Text_shadow]
|
||||
"""文本阴影参数, None表示无阴影"""
|
||||
|
||||
bubble: Optional[TextBubble]
|
||||
"""文本气泡效果, 在放入轨道时加入素材列表中"""
|
||||
effect: Optional[TextEffect]
|
||||
@@ -225,10 +317,14 @@ class Text_segment(Visual_segment):
|
||||
fixed_height: float
|
||||
"""固定高度, -1表示不固定"""
|
||||
|
||||
text_styles: List[TextStyleRange]
|
||||
"""文本的多种样式列表"""
|
||||
|
||||
def __init__(self, text: str, timerange: Timerange, *,
|
||||
font: Optional[Font_type] = None,
|
||||
style: Optional[Text_style] = None, clip_settings: Optional[Clip_settings] = None,
|
||||
border: Optional[Text_border] = None, background: Optional[Text_background] = None,
|
||||
shadow: Optional[Text_shadow] = None,
|
||||
fixed_width: int = -1, fixed_height: int = -1):
|
||||
"""创建文本片段, 并指定其时间信息、字体样式及图像调节设置
|
||||
|
||||
@@ -252,12 +348,21 @@ class Text_segment(Visual_segment):
|
||||
self.style = style or Text_style()
|
||||
self.border = border
|
||||
self.background = background
|
||||
self.shadow = shadow
|
||||
|
||||
self.bubble = None
|
||||
self.effect = None
|
||||
|
||||
self.fixed_width = fixed_width
|
||||
self.fixed_height = fixed_height
|
||||
self.text_styles = []
|
||||
|
||||
# 修改设置特定范围的文本样式的方法
|
||||
def add_text_style(self, textStyleRange: TextStyleRange) -> "Text_segment":
|
||||
# 添加新的样式范围
|
||||
self.text_styles.append(textStyleRange)
|
||||
return self
|
||||
|
||||
|
||||
@classmethod
|
||||
def create_from_template(cls, text: str, timerange: Timerange, template: "Text_segment") -> "Text_segment":
|
||||
@@ -341,10 +446,194 @@ class Text_segment(Visual_segment):
|
||||
check_flag |= 8
|
||||
if self.background:
|
||||
check_flag |= 16
|
||||
if self.shadow and self.shadow.has_shadow: # 如果有阴影且启用了阴影
|
||||
check_flag |= 32 # 添加阴影标志
|
||||
|
||||
content_json = {
|
||||
"styles": [
|
||||
# 构建styles数组
|
||||
styles = []
|
||||
|
||||
if self.text_styles:
|
||||
# 创建一个排序后的样式范围列表
|
||||
sorted_styles = sorted(self.text_styles, key=lambda x: x.start)
|
||||
|
||||
# 检查是否需要在开头添加默认样式
|
||||
if sorted_styles[0].start > 0:
|
||||
# 添加从0到第一个样式开始的默认样式
|
||||
default_style = {
|
||||
"fill": {
|
||||
"alpha": 1.0,
|
||||
"content": {
|
||||
"render_type": "solid",
|
||||
"solid": {
|
||||
"alpha": self.style.alpha,
|
||||
"color": list(self.style.color)
|
||||
}
|
||||
}
|
||||
},
|
||||
"range": [0, sorted_styles[0].start],
|
||||
"size": self.style.size,
|
||||
"bold": self.style.bold,
|
||||
"italic": self.style.italic,
|
||||
"underline": self.style.underline,
|
||||
"strokes": [self.border.export_json()] if self.border else []
|
||||
}
|
||||
|
||||
# 如果有阴影设置,添加到样式中
|
||||
if self.shadow and self.shadow.has_shadow:
|
||||
style_item["shadows"] = [
|
||||
{
|
||||
"diffuse": self.shadow.smoothing / 6, # diffuse = smoothing/6
|
||||
"angle": self.shadow.angle,
|
||||
"content": {
|
||||
"solid": {
|
||||
"color": [int(self.shadow.color[1:3], 16)/255,
|
||||
int(self.shadow.color[3:5], 16)/255,
|
||||
int(self.shadow.color[5:7], 16)/255]
|
||||
}
|
||||
},
|
||||
"distance": self.shadow.distance,
|
||||
"alpha": self.shadow.alpha
|
||||
}
|
||||
]
|
||||
|
||||
# 如果有全局字体设置,添加到样式中
|
||||
if self.font:
|
||||
default_style["font"] = {
|
||||
"id": self.font.resource_id,
|
||||
"path": "C:/%s.ttf" % self.font.name
|
||||
}
|
||||
|
||||
# 如果有特效设置,添加到样式中
|
||||
if self.effect:
|
||||
default_style["effectStyle"] = {
|
||||
"id": self.effect.effect_id,
|
||||
"path": "C:" # 并不会真正在此处放置素材文件
|
||||
}
|
||||
|
||||
styles.append(default_style)
|
||||
|
||||
# 处理每个样式范围
|
||||
for i, style_range in enumerate(sorted_styles):
|
||||
# 添加当前样式范围的样式
|
||||
style_item = {
|
||||
"fill": {
|
||||
"alpha": 1.0,
|
||||
"content": {
|
||||
"render_type": "solid",
|
||||
"solid": {
|
||||
"alpha": style_range.style.alpha,
|
||||
"color": list(style_range.style.color)
|
||||
}
|
||||
}
|
||||
},
|
||||
"range": style_range.get_range(),
|
||||
"size": style_range.style.size,
|
||||
"bold": style_range.style.bold,
|
||||
"italic": style_range.style.italic,
|
||||
"underline": style_range.style.underline,
|
||||
"strokes": [style_range.border.export_json()] if style_range.border else []
|
||||
}
|
||||
|
||||
# 如果TextStyleRange有字体设置,优先使用它
|
||||
if hasattr(style_range, 'font') and style_range.font:
|
||||
style_item["font"] = {
|
||||
"id": style_range.font.resource_id,
|
||||
"path": "C:/%s.ttf" % style_range.font.name
|
||||
}
|
||||
# 否则,如果有全局字体设置,使用全局字体
|
||||
elif self.font:
|
||||
style_item["font"] = {
|
||||
"id": self.font.resource_id,
|
||||
"path": "C:/%s.ttf" % self.font.name
|
||||
}
|
||||
|
||||
# 如果有特效设置,添加到样式中
|
||||
if self.effect:
|
||||
style_item["effectStyle"] = {
|
||||
"id": self.effect.effect_id,
|
||||
"path": "C:" # 并不会真正在此处放置素材文件
|
||||
}
|
||||
|
||||
styles.append(style_item)
|
||||
|
||||
# 检查是否需要在当前样式和下一个样式之间添加默认样式
|
||||
if i < len(sorted_styles) - 1 and style_range.end < sorted_styles[i+1].start:
|
||||
# 添加从当前样式结束到下一个样式开始的默认样式
|
||||
gap_style = {
|
||||
"fill": {
|
||||
"alpha": 1.0,
|
||||
"content": {
|
||||
"render_type": "solid",
|
||||
"solid": {
|
||||
"alpha": self.style.alpha,
|
||||
"color": list(self.style.color)
|
||||
}
|
||||
}
|
||||
},
|
||||
"range": [style_range.end, sorted_styles[i+1].start],
|
||||
"size": self.style.size,
|
||||
"bold": self.style.bold,
|
||||
"italic": self.style.italic,
|
||||
"underline": self.style.underline,
|
||||
"strokes": [self.border.export_json()] if self.border else []
|
||||
}
|
||||
|
||||
# 如果有全局字体设置,添加到样式中
|
||||
if self.font:
|
||||
gap_style["font"] = {
|
||||
"id": self.font.resource_id,
|
||||
"path": "C:/%s.ttf" % self.font.name
|
||||
}
|
||||
|
||||
# 如果有特效设置,添加到样式中
|
||||
if self.effect:
|
||||
gap_style["effectStyle"] = {
|
||||
"id": self.effect.effect_id,
|
||||
"path": "C:" # 并不会真正在此处放置素材文件
|
||||
}
|
||||
|
||||
styles.append(gap_style)
|
||||
|
||||
# 检查是否需要在最后一个样式之后添加默认样式
|
||||
if sorted_styles[-1].end < len(self.text):
|
||||
# 添加从最后一个样式结束到文本结尾的默认样式
|
||||
end_style = {
|
||||
"fill": {
|
||||
"alpha": 1.0,
|
||||
"content": {
|
||||
"render_type": "solid",
|
||||
"solid": {
|
||||
"alpha": self.style.alpha,
|
||||
"color": list(self.style.color)
|
||||
}
|
||||
}
|
||||
},
|
||||
"range": [sorted_styles[-1].end, len(self.text)],
|
||||
"size": self.style.size,
|
||||
"bold": self.style.bold,
|
||||
"italic": self.style.italic,
|
||||
"underline": self.style.underline,
|
||||
"strokes": [self.border.export_json()] if self.border else []
|
||||
}
|
||||
|
||||
# 如果有全局字体设置,添加到样式中
|
||||
if self.font:
|
||||
end_style["font"] = {
|
||||
"id": self.font.resource_id,
|
||||
"path": "C:/%s.ttf" % self.font.name
|
||||
}
|
||||
|
||||
# 如果有特效设置,添加到样式中
|
||||
if self.effect:
|
||||
end_style["effectStyle"] = {
|
||||
"id": self.effect.effect_id,
|
||||
"path": "C:" # 并不会真正在此处放置素材文件
|
||||
}
|
||||
|
||||
styles.append(end_style)
|
||||
else:
|
||||
# 如果text_styles为空,使用全局样式创建一个默认的style
|
||||
style_item = {
|
||||
"fill": {
|
||||
"alpha": 1.0,
|
||||
"content": {
|
||||
@@ -362,20 +651,46 @@ class Text_segment(Visual_segment):
|
||||
"underline": self.style.underline,
|
||||
"strokes": [self.border.export_json()] if self.border else []
|
||||
}
|
||||
],
|
||||
"text": self.text
|
||||
|
||||
# 如果有阴影设置,添加到样式中
|
||||
if self.shadow and self.shadow.has_shadow:
|
||||
style_item["shadows"] = [
|
||||
{
|
||||
"diffuse": self.shadow.smoothing / 6, # diffuse = smoothing/6
|
||||
"angle": self.shadow.angle,
|
||||
"content": {
|
||||
"solid": {
|
||||
"color": [int(self.shadow.color[1:3], 16)/255,
|
||||
int(self.shadow.color[3:5], 16)/255,
|
||||
int(self.shadow.color[5:7], 16)/255]
|
||||
}
|
||||
},
|
||||
"distance": self.shadow.distance,
|
||||
"alpha": self.shadow.alpha
|
||||
}
|
||||
]
|
||||
|
||||
# 如果有全局字体设置,添加到样式中
|
||||
if self.font:
|
||||
content_json["styles"][0]["font"] = {
|
||||
style_item["font"] = {
|
||||
"id": self.font.resource_id,
|
||||
"path": "C:/%s.ttf" % self.font.name # 并不会真正在此处放置字体文件
|
||||
}
|
||||
|
||||
# 如果有特效设置,添加到样式中
|
||||
if self.effect:
|
||||
content_json["styles"][0]["effectStyle"] = {
|
||||
style_item["effectStyle"] = {
|
||||
"id": self.effect.effect_id,
|
||||
"path": "C:" # 并不会真正在此处放置素材文件
|
||||
}
|
||||
|
||||
styles.append(style_item)
|
||||
|
||||
content_json = {
|
||||
"styles": styles,
|
||||
"text": self.text
|
||||
}
|
||||
|
||||
ret = {
|
||||
"id": self.material_id,
|
||||
"content": json.dumps(content_json, ensure_ascii=False),
|
||||
@@ -413,19 +728,34 @@ class Text_segment(Visual_segment):
|
||||
# },
|
||||
# "shadow_smoothing": 0.45,
|
||||
|
||||
# 整体字体设置, 似乎会被content覆盖
|
||||
# "font_category_id": "",
|
||||
# "font_category_name": "",
|
||||
# "font_id": "",
|
||||
# "font_name": "",
|
||||
# "font_path": "",
|
||||
# "font_resource_id": "",
|
||||
# "font_size": 15.0,
|
||||
# "font_source_platform": 0,
|
||||
# "font_team_id": "",
|
||||
# "font_title": "none",
|
||||
# "font_url": "",
|
||||
# "fonts": [],
|
||||
# 整体字体设置
|
||||
"font_category_id": "",
|
||||
"font_category_name": "",
|
||||
"font_id": "",
|
||||
"font_name": "",
|
||||
"font_path": "",
|
||||
"font_resource_id": "",
|
||||
"font_size": 15.0,
|
||||
"font_source_platform": 0,
|
||||
"font_team_id": "",
|
||||
"font_title": "none",
|
||||
"font_url": "",
|
||||
"fonts": [] if not self.text_styles else [
|
||||
# 根据text_styles生成fonts数组
|
||||
*[{
|
||||
"category_id": "preset",
|
||||
"category_name": "剪映预设",
|
||||
"effect_id": style_range.font.resource_id if hasattr(style_range, 'font') and style_range.font else (self.font.resource_id if self.font else ""),
|
||||
"file_uri": "",
|
||||
"id": "BFBA9655-1FE5-41A0-A85D-577EFFF17BDD",
|
||||
"path": "C:/%s.ttf" % (style_range.font.name if hasattr(style_range, 'font') and style_range.font else (self.font.name if self.font else "")),
|
||||
"request_id": "20250713102314DA3D8F267527925ADC9A",
|
||||
"resource_id": style_range.font.resource_id if hasattr(style_range, 'font') and style_range.font else (self.font.resource_id if self.font else ""),
|
||||
"source_platform": 0,
|
||||
"team_id": "",
|
||||
"title": style_range.font.name if hasattr(style_range, 'font') and style_range.font else (self.font.name if self.font else "")
|
||||
} for style_range in self.text_styles if (hasattr(style_range, 'font') and style_range.font) or self.font]
|
||||
],
|
||||
|
||||
# 似乎会被content覆盖
|
||||
# "text_alpha": 1.0,
|
||||
@@ -439,4 +769,9 @@ class Text_segment(Visual_segment):
|
||||
if self.background:
|
||||
ret.update(self.background.export_json())
|
||||
|
||||
# 添加阴影参数
|
||||
if self.shadow and self.shadow.has_shadow:
|
||||
shadow_json = self.shadow.export_json()
|
||||
ret.update(shadow_json) # 将阴影参数合并到返回的字典中
|
||||
|
||||
return ret
|
||||
|
||||
Reference in New Issue
Block a user