Skip to main content
Glama

JianYing MCP

by hey-jian-wei
text_segment.py17.3 kB
"""定义文本片段及其相关类""" import json import uuid from copy import deepcopy from typing import Dict, Tuple, Any from typing import Union, Optional, Literal from .time_util import Timerange, tim from .segment import ClipSettings, VisualSegment from .animation import SegmentAnimations, Text_animation from .metadata import FontType, EffectMeta from .metadata import TextIntro, TextOutro, TextLoopAnim class TextStyle: """字体样式类""" size: float """字体大小""" bold: bool """是否加粗""" italic: bool """是否斜体""" underline: bool """是否加下划线""" color: Tuple[float, float, float] """字体颜色, RGB三元组, 取值范围为[0, 1]""" alpha: float """字体不透明度""" align: Literal[0, 1, 2] """对齐方式""" vertical: bool """是否为竖排文本""" letter_spacing: int """字符间距""" line_spacing: int """行间距""" auto_wrapping: bool """是否自动换行""" max_line_width: float """最大行宽, 取值范围为[0, 1]""" def __init__(self, *, size: float = 6.0, bold: bool = False, italic: bool = False, underline: bool = False, color: Tuple[float, float, float] = (1.0, 1.0, 1.0), alpha: float = 1.0, align: Literal[0, 1, 2] = 0, vertical: bool = False, letter_spacing: int = 0, line_spacing: int = 0, auto_wrapping: bool = False, max_line_width: float = 0.82): """ Args: size (`float`, optional): 字体大小, 默认为6.0 bold (`bool`, optional): 是否加粗, 默认为否 italic (`bool`, optional): 是否斜体, 默认为否 underline (`bool`, optional): 是否加下划线, 默认为否 color (`Tuple[float, float, float]`, optional): 字体颜色, RGB三元组, 取值范围为[0, 1], 默认为白色 alpha (`float`, optional): 字体不透明度, 取值范围[0, 1], 默认不透明 align (`int`, optional): 对齐方式, 0: 左对齐, 1: 居中, 2: 右对齐, 默 认为左对齐 vertical (`bool`, optional): 是否为竖排文本, 默认为否 letter_spacing (`int`, optional): 字符间距, 定义与剪映中一致, 默认为0 line_spacing (`int`, optional): 行间距, 定义与剪映中一致, 默认为0 auto_wrapping (`bool`, optional): 是否自动换行, 默认关闭 max_line_width (`float`, optional): 每行最大行宽占屏幕宽度比例, 取值范围为[0, 1], 默认为0.82 """ self.size = size self.bold = bold self.italic = italic self.underline = underline self.color = color self.alpha = alpha self.align = align self.vertical = vertical self.letter_spacing = letter_spacing self.line_spacing = line_spacing self.auto_wrapping = auto_wrapping self.max_line_width = max_line_width class TextBorder: """文本描边的参数""" alpha: float """描边不透明度""" color: Tuple[float, float, float] """描边颜色, RGB三元组, 取值范围为[0, 1]""" width: float """描边宽度""" def __init__(self, *, alpha: float = 1.0, color: Tuple[float, float, float] = (0.0, 0.0, 0.0), width: float = 40.0): """ Args: alpha (`float`, optional): 描边不透明度, 取值范围[0, 1], 默认为1.0 color (`Tuple[float, float, float]`, optional): 描边颜色, RGB三元组, 取值范围为[0, 1], 默认为黑色 width (`float`, optional): 描边宽度, 与剪映中一致, 取值范围为[0, 100], 默认为40.0 """ self.alpha = alpha self.color = color self.width = width / 100.0 * 0.2 # 此映射可能不完全正确 def export_json(self) -> Dict[str, Any]: """导出JSON数据, 放置在素材content的styles中""" return { "content": { "solid": { "alpha": self.alpha, "color": list(self.color), } }, "width": self.width } class TextBackground: """文本背景参数""" style: Literal[1, 2] """背景样式""" alpha: float """背景不透明度""" color: str """背景颜色, 格式为'#RRGGBB'""" round_radius: float """背景圆角半径""" height: float """背景高度""" width: float """背景宽度""" horizontal_offset: float """背景水平偏移""" vertical_offset: float """背景竖直偏移""" def __init__(self, *, color: str, style: Literal[1, 2] = 1, alpha: float = 1.0, round_radius: float = 0.0, height: float = 0.14, width: float = 0.14, horizontal_offset: float = 0.5, vertical_offset: float = 0.5): """ Args: color (`str`): 背景颜色, 格式为'#RRGGBB' style (`int`, optional): 背景样式, 1和2分别对应剪映中的两种样式, 默认为1 alpha (`float`, optional): 背景不透明度, 与剪映中一致, 取值范围[0, 1], 默认为1.0 round_radius (`float`, optional): 背景圆角半径, 与剪映中一致, 取值范围[0, 1], 默认为0.0 height (`float`, optional): 背景高度, 与剪映中一致, 取值范围为[0, 1], 默认为0.14 width (`float`, optional): 背景宽度, 与剪映中一致, 取值范围为[0, 1], 默认为0.14 horizontal_offset (`float`, optional): 背景水平偏移, 与剪映中一致, 取值范围为[0, 1], 默认为0.5 vertical_offset (`float`, optional): 背景竖直偏移, 与剪映中一致, 取值范围为[0, 1], 默认为0.5 """ self.style = style self.alpha = alpha self.color = color self.round_radius = round_radius self.height = height self.width = width self.horizontal_offset = horizontal_offset * 2 - 1 self.vertical_offset = vertical_offset * 2 - 1 def export_json(self) -> Dict[str, Any]: """生成子JSON数据, 在TextSegment导出时合并到其中""" return { "background_style": self.style, "background_color": self.color, "background_alpha": self.alpha, "background_round_radius": self.round_radius, "background_height": self.height, "background_width": self.width, "background_horizontal_offset": self.horizontal_offset, "background_vertical_offset": self.vertical_offset, } class TextBubble: """文本气泡素材, 与滤镜素材本质上一致""" global_id: str """气泡全局id, 由程序自动生成""" effect_id: str resource_id: str def __init__(self, effect_id: str, resource_id: str): self.global_id = uuid.uuid4().hex self.effect_id = effect_id self.resource_id = resource_id def export_json(self) -> Dict[str, Any]: return { "apply_target_type": 0, "effect_id": self.effect_id, "id": self.global_id, "resource_id": self.resource_id, "type": "text_shape", "value": 1.0, # 不导出path和request_id } class TextEffect(TextBubble): """文本花字素材, 与滤镜素材本质上也一致""" def export_json(self) -> Dict[str, Any]: ret = super().export_json() ret["type"] = "text_effect" ret["source_platform"] = 1 return ret class TextSegment(VisualSegment): """文本片段类, 目前仅支持设置基本的字体样式""" text: str """文本内容""" font: Optional[EffectMeta] """字体类型""" style: TextStyle """字体样式""" border: Optional[TextBorder] """文本描边参数, None表示无描边""" background: Optional[TextBackground] """文本背景参数, None表示无背景""" bubble: Optional[TextBubble] """文本气泡效果, 在放入轨道时加入素材列表中""" effect: Optional[TextEffect] """文本花字效果, 在放入轨道时加入素材列表中, 目前仅支持一部分花字效果""" def __init__(self, text: str, timerange: Timerange, *, font: Optional[FontType] = None, style: Optional[TextStyle] = None, clip_settings: Optional[ClipSettings] = None, border: Optional[TextBorder] = None, background: Optional[TextBackground] = None): """创建文本片段, 并指定其时间信息、字体样式及图像调节设置 片段创建完成后, 可通过`ScriptFile.add_segment`方法将其添加到轨道中 Args: text (`str`): 文本内容 timerange (`Timerange`): 片段在轨道上的时间范围 font (`Font_type`, optional): 字体类型, 默认为系统字体 style (`TextStyle`, optional): 字体样式, 包含大小/颜色/对齐/透明度等. clip_settings (`ClipSettings`, optional): 图像调节设置, 默认不做任何变换 border (`TextBorder`, optional): 文本描边参数, 默认无描边 background (`TextBackground`, optional): 文本背景参数, 默认无背景 """ super().__init__(uuid.uuid4().hex, None, timerange, 1.0, 1.0, False, clip_settings=clip_settings) self.text = text self.font = font.value if font else None self.style = style or TextStyle() self.border = border self.background = background self.bubble = None self.effect = None @classmethod def create_from_template(cls, text: str, timerange: Timerange, template: "TextSegment") -> "TextSegment": """根据模板创建新的文本片段, 并指定其文本内容""" new_segment = cls(text, timerange, style=deepcopy(template.style), clip_settings=deepcopy(template.clip_settings), border=deepcopy(template.border), background=deepcopy(template.background)) new_segment.font = deepcopy(template.font) # 处理动画等 if template.animations_instance: new_segment.animations_instance = deepcopy(template.animations_instance) new_segment.animations_instance.animation_id = uuid.uuid4().hex new_segment.extra_material_refs.append(new_segment.animations_instance.animation_id) if template.bubble: new_segment.add_bubble(template.bubble.effect_id, template.bubble.resource_id) if template.effect: new_segment.add_effect(template.effect.effect_id) return new_segment def add_animation(self, animation_type: Union[TextIntro, TextOutro, TextLoopAnim], duration: Union[str, float, None] = None) -> "TextSegment": """将给定的入场/出场/循环动画添加到此片段的动画列表中, 出入场动画的持续时间可以自行设置, 循环动画则会自动填满其余无动画部分 注意: 若希望同时使用循环动画和入出场动画, 请**先添加出入场动画再添加循环动画** Args: animation_type (`TextIntro`, `TextOutro` or `TextLoopAnim`): 文本动画类型. duration (`str` or `float`, optional): 动画持续时间, 单位为微秒, 仅对入场/出场动画有效. 若传入字符串则会调用`tim()`函数进行解析. 默认使用动画的时长 """ if duration is None: duration = animation_type.value.duration duration = min(tim(duration), self.target_timerange.duration) if isinstance(animation_type, TextIntro): start = 0 elif isinstance(animation_type, TextOutro): start = self.target_timerange.duration - duration elif isinstance(animation_type, TextLoopAnim): intro_trange = self.animations_instance and self.animations_instance.get_animation_trange("in") outro_trange = self.animations_instance and self.animations_instance.get_animation_trange("out") start = intro_trange.start if intro_trange else 0 duration = self.target_timerange.duration - start - (outro_trange.duration if outro_trange else 0) else: raise TypeError("Invalid animation type %s" % type(animation_type)) if self.animations_instance is None: self.animations_instance = SegmentAnimations() self.extra_material_refs.append(self.animations_instance.animation_id) self.animations_instance.add_animation(Text_animation(animation_type, start, duration)) return self def add_bubble(self, effect_id: str, resource_id: str) -> "TextSegment": """根据素材信息添加气泡效果, 相应素材信息可通过`ScriptFile.inspect_material`从模板中获取 Args: effect_id (`str`): 气泡效果的effect_id resource_id (`str`): 气泡效果的resource_id """ self.bubble = TextBubble(effect_id, resource_id) self.extra_material_refs.append(self.bubble.global_id) return self def add_effect(self, effect_id: str) -> "TextSegment": """根据素材信息添加花字效果, 相应素材信息可通过`ScriptFile.inspect_material`从模板中获取 Args: effect_id (`str`): 花字效果的effect_id, 也同时是其resource_id """ self.effect = TextEffect(effect_id, effect_id) self.extra_material_refs.append(self.effect.global_id) return self def export_material(self) -> Dict[str, Any]: """与此文本片段联系的素材, 以此不再单独定义Text_material类""" # 叠加各类效果的flag check_flag: int = 7 if self.border: check_flag |= 8 if self.background: check_flag |= 16 content_json = { "styles": [ { "fill": { "alpha": 1.0, "content": { "render_type": "solid", "solid": { "alpha": 1.0, "color": list(self.style.color) } } }, "range": [0, 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 [] } ], "text": self.text } if self.font: content_json["styles"][0]["font"] = { "id": self.font.resource_id, "path": "C:/%s.ttf" % self.font.name # 并不会真正在此处放置字体文件 } if self.effect: content_json["styles"][0]["effectStyle"] = { "id": self.effect.effect_id, "path": "C:" # 并不会真正在此处放置素材文件 } ret = { "id": self.material_id, "content": json.dumps(content_json, ensure_ascii=False), "typesetting": int(self.style.vertical), "alignment": self.style.align, "letter_spacing": self.style.letter_spacing * 0.05, "line_spacing": 0.02 + self.style.line_spacing * 0.05, "line_feed": 1, "line_max_width": self.style.max_line_width, "force_apply_line_max_width": False, "check_flag": check_flag, "type": "subtitle" if self.style.auto_wrapping else "text", # 混合 (+4) "global_alpha": self.style.alpha, # 发光 (+64),属性由extra_material_refs记录 # 阴影 (+32) # "has_shadow": False, # "shadow_alpha": 0.9, # "shadow_angle": -45.0, # "shadow_color": "", # "shadow_distance": 5.0, # "shadow_point": { # "x": 0.6363961030678928, # "y": -0.6363961030678928 # }, # "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": [], # 似乎会被content覆盖 # "text_alpha": 1.0, # "text_color": "#FFFFFF", # "text_curve": None, # "text_preset_resource_id": "", # "text_size": 30, # "underline": False, } if self.background: ret.update(self.background.export_json()) return ret

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hey-jian-wei/jianying-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server