GuanYixuan / pyJianYingDraft

轻量、灵活、易上手的Python剪映草稿文件生成工具
37 stars 7 forks source link

关于添加文字描边功能。 #7

Open pretty2015 opened 2 days ago

pretty2015 commented 2 days ago

·我看软件里有这功能,但项目里没有。然后偿试修改Text_style类。

class Text_style:
    # ... 现有的代码 ...

    bord_alpha: float
    """描边透明度"""
    bord_color: Tuple[float, float, float]
    """描边颜色"""
    bord_width: float
    """描边宽度"""

    def __init__(self, *, size: float = 8.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,
                 bord_alpha: float = 1.0, bord_color: Tuple[float, float, float] = (0.0, 0.0, 0.0),
                 bord_width: float = 0.08):
        # ... 现有的代码 ...
        self.bord_alpha = bord_alpha
        self.bord_color = bord_color
        self.bord_width = bord_width

然后没有成功。这三个变量名是参照你下面 export_material 中的名字写的。 但加入后,原始json中content字段也没有出现相关参数名。 所以可能没有写入到json中(但运行没报错)。 问题一,如查添加字些文字的变量到Text_style中,还请赐教下大致逻辑。 二是现在网上的安装包都是6+的版本。没办想找到参数名,能不能给个5.9的安装包下载地址好研究完善这个项目。 感谢。

GuanYixuan commented 2 days ago

添加描边的大致逻辑是先对Text_style做修改,然后export_material中的相应行也得解除注释并导出self.style中的相应参数。除此之外,可能还要对check_flag加8来把剪映里“描边”的那个复选框打上勾

关于下载路径,似乎还可以下载

pretty2015 commented 1 day ago

节日快乐! 我按你的思路改了下。还是没成功(原始json中也没有相关字符)。我下载了5.9版的jingying,但一点草稿就提示我升级(也运行了补丁),所以暂时没有提到描边的具体参数。下面是我修改后text_segment的源码,请赐教!

"""定义文本片段及其相关类"""

import uuid, json

from typing import Dict, List, Tuple, Any
from typing import Optional, Literal

from .time_util import Timerange
from .segment import Base_segment, Clip_settings

class Text_style:
    """字体样式类"""

    size: float
    """字体大小"""

    bold: bool
    """是否加粗"""
    italic: bool
    """是否斜体"""
    underline: bool
    """是否加下划线"""

    color: Tuple[float, float, float]
    """字体颜色"""
    alpha: float
    """字体透明度"""

    align: Literal[0, 1, 2]
    """对齐方式"""
    vertical: bool
    """竖排文本"""

    bord_alpha: float
    """描边透明度"""
    bord_color: Tuple[float, float, float]
    """描边颜色"""
    bord_width: float
    """描边宽度"""

    def __init__(self, *, size: float = 8.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,
                 bord_alpha: float = 1.0, bord_color: Tuple[float, float, float] = (0.0, 0.0, 0.0),
                 bord_width: float = 0.08):
        """
        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): 是否是竖排文本, 默认为否
        """
        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.bord_alpha = bord_alpha
        self.bord_color = bord_color
        self.bord_width = bord_width

class Text_segment(Base_segment):
    """文本片段类, 目前仅支持设置基本的字体样式"""

    text: str
    """文本内容"""
    style: Text_style
    """字体样式"""

    clip_settings: Clip_settings
    """图像调节设置"""

    extra_material_refs: List[str]
    """附加的素材id列表, 用于链接动画/特效等"""

    def __init__(self, text: str, timerange: Timerange, *,
                 style: Optional[Text_style] = None, clip_settings: Optional[Clip_settings] = None):
        """创建文本片段, 并指定其时间信息、字体样式及图像调节设置

        片段创建完成后, 可通过`Script_file.add_segment`方法将其添加到轨道中

        Args:
            text (`str`): 文本内容
            timerange (`Timerange`): 片段在轨道上的时间范围
            style (`Text_style`, optional): 字体样式
            clip_settings (`Clip_settings`, optional): 图像调节设置, 默认不做任何变换
        """
        super().__init__(uuid.uuid4().hex, timerange)

        self.text = text
        self.style = style or Text_style()
        self.clip_settings = clip_settings or Clip_settings()

        self.extra_material_refs = []

    def export_material(self) -> Dict[str, Any]:
        """与此文本片段联系的素材, 以此不再单独定义Text_material类"""
        ret = {
            "add_type": 0,

            "typesetting": int(self.style.vertical),
            "alignment": self.style.align,

            # ?
            # "caption_template_info": {
            #     "category_id": "",
            #     "category_name": "",
            #     "effect_id": "",
            #     "is_new": False,
            #     "path": "",
            #     "request_id": "",
            #     "resource_id": "",
            #     "resource_name": "",
            #     "source_platform": 0
            # },

            # 混合 (+4)
            # "global_alpha": 1.0,

            # 描边 (+8)
            "border_alpha": self.style.bord_alpha,
            "border_color": "#{:02x}{:02x}{:02x}".format(
                int(self.style.bord_color[0] * 255),
                int(self.style.bord_color[1] * 255),
                int(self.style.bord_color[2] * 255)
            ),
            "border_width": self.style.bord_width,

            # 背景 (+16)
            # "background_style": 0,
            # "background_color": "",
            # "background_alpha": 1.0,
            # "background_round_radius": 0.0,
            # "background_height": 0.14,
            # "background_width": 0.14,
            # "background_horizontal_offset": 0.0,
            # "background_vertical_offset": 0.0,

            # 发光 (+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,

            "base_content": "",
            "bold_width": 0.0,

            "check_flag": 7,
            "combo_info": {
                "text_templates": []
            },
            "content": json.dumps({
                "styles": [
                    {
                        "fill": {
                            "alpha": 1.0,
                            "content": {
                                "render_type": "solid",
                                "solid": {
                                    "alpha": self.style.alpha,
                                    "color": list(self.style.color)
                                }
                            }
                        },
                        # "font": {
                        #     "id": "",
                        #     "path": "***.ttf"
                        # },
                        "range": [0, len(self.text)],
                        "size": self.style.size,
                        "bold": self.style.bold,
                        "italic": self.style.italic,
                        "underline": self.style.underline,
                    }
                ],
                "text": self.text
            }),
            "fixed_height": -1.0,
            "fixed_width": -1.0,
            "force_apply_line_max_width": False,

            "group_id": "",

            "id": self.material_id,
            "initial_scale": 1.0,
            "inner_padding": -1.0,
            "is_rich_text": False,
            "italic_degree": 0,
            "ktv_color": "",
            "language": "",
            "layer_weight": 1,
            "letter_spacing": 0.0,
            "line_feed": 1,
            "line_max_width": 0.82,
            "line_spacing": 0.02,
            "multi_language_current": "none",
            "name": "",
            "original_size": [],

            "preset_category": "",
            "preset_category_id": "",
            "preset_has_set_alignment": False,
            "preset_id": "",
            "preset_index": 0,
            "preset_name": "",

            "recognize_task_id": "",
            "recognize_type": 0,
            "relevance_segment": [],

            "shape_clip_x": False,
            "shape_clip_y": False,
            "source_from": "",
            "style_name": "",
            "sub_type": 0,
            "subtitle_keywords": None,
            "subtitle_template_original_fontsize": 0.0,
            "text_to_audio_ids": [],
            "tts_auto_update": False,
            "type": "text",

            "underline_offset": 0.22,
            "underline_width": 0.05,

            "use_effect_default_color": True,
            "words": {
                "end_time": [],
                "start_time": [],
                "text": []
            }
        }

        # 更新 check_flag 以包含描边效果
        ret["check_flag"] |= 8

        # 在 content 中添加描边信息
        content = json.loads(ret["content"])
        for style in content["styles"]:
            style["stroke"] = {
                "alpha": self.style.bord_alpha,
                "color": list(self.style.bord_color),
                "width": self.style.bord_width
            }
        ret["content"] = json.dumps(content)

        return ret

    def export_json(self) -> Dict[str, Any]:
        ret = super().export_json()
        ret.update({
            # 与Video_segment一致的部分
            "clip": self.clip_settings.export_json(),
            # "hdr_settings": null,
            # "uniform_scale": {},

            # 与Media_segment一致的部分
            "source_timerange": None,
            "speed": 1.0,
            "volume": 1.0,
            "extra_material_refs": [self.extra_material_refs],
        })
        return ret
GuanYixuan commented 1 day ago

感谢,也祝你节日快乐

关于草稿,来自高版本的草稿导到5.9中它不会主动帮你降级,应该需要在5.9下新创建草稿才能获得低版本的未加密草稿。(当然它还会时不时推荐你升级到6.*版本,关掉那个弹窗就好)

代码方面,我看了一下其实已经很接近了,应该是styles里面strokes部分的结构不太对,所以还是能够看到json的原始参数才方便写。我在你的基础上修改了一下,现在应该已经可用了,参见代码库的更新