[
  {
    "path": ".gitignore",
    "content": "__pycache__"
  },
  {
    "path": "README.md",
    "content": "# JianYingProDraft\n\n## 说明\n\n1. 实现原理 : `JianYingPro` 项目文件是 `json` 的形式存储的，只需要创建`draft_content.json`,`draft_mate_info.json` 打开软件后会自动补全。\n2. 添加一个媒体到轨道顺序 `草稿媒体库` -> `内容媒体库`-> `轨道片段`\n3. `add_media_to_track` 会识别媒体类型，加入到对应轨道。\n4. 当没有视频轨道时，创建音频轨道会先创建视频轨道。\n\n```python\nif __name__ == \"__main__\":\n    # 新建草稿\n    draft = Draft(\"测试草稿\") \n    # 将媒体转化为草稿素材\n    audio = Material(\"D:/Music/Krubb Wenkroist - Bleach.mp3\")\n    # 将媒体添加到轨道中\n    draft.add_media_to_track(audio)\n    draft.add_media_to_track('D:/Videos/剪印导出/测试1(1).mp4')\n    # 保存草稿\n    draft.save()\n```\n\n使用前先修改`main.py`中的草稿文件夹路径\n\n```python\ndrafts_folder = \"D:/JianyingPro Drafts\"\n```\n"
  },
  {
    "path": "draft/Draft.py",
    "content": ""
  },
  {
    "path": "draft/__init__.py",
    "content": "import os\nimport time\nimport util\nimport template\nfrom material import Material\nfrom track import Tracks\n\nclass Draft:\n\n    drafts_folder = \"D:/JianyingPro Drafts\"\n    template_folder = \"./template json/\"\n    content_file = \"draft_content.json\"\n    meta_info_file = \"draft_meta_info.json\"\n\n    def __init__(self, name:str=\"Test\"):\n        # 路径变量\n        self.name = name\n        self.folder = os.path.join(self.drafts_folder, name)\n        self.content_path = os.path.join(self.folder, self.content_file)\n        self.meta_info_path = os.path.join(self.folder, self.meta_info_file)\n        # 新建项目文件夹\n        util.new_folder(self.folder)\n        # 读取草稿模板\n        self.content = util.read_json(os.path.join(self.template_folder,self.content_file))\n        self.meta_info = util.read_json(os.path.join(self.template_folder,self.meta_info_file))\n        # 初始化草稿内容信息\n        self.content['id'] = util.generate_id()\n        # 初始化素材信息\n        self.meta_info['id'] = util.generate_id()\n        self.meta_info['draft_fold_path'] = self.folder.replace(\"\\\\\",'/')\n        self.meta_info['draft_timeline_metetyperials_size_'] = 0\n        self.meta_info['tm_draft_create'] = time.time()\n        self.meta_info['tm_draft_modified'] = time.time()\n        self.meta_info['draft_root_path'] = self.drafts_folder.replace(\"/\",\"\\\\\")\n        self.meta_info['draft_removable_storage_device'] = self.drafts_folder.split(':/')[0]\n        # 创建变量\n        self.draft_materials:list = self.meta_info['draft_materials'][0]['value'] # 草稿素材库\n        self.content_materials:list = self.content['materials'] # 内容素材库\n        self.tracks = Tracks() # 轨道\n        self.materials = {}\n\n    def _medai_tpye(self,media):\n        if type(media) == str:\n            # 当media为文件路径时\n            if os.path.exists(media):\n                return 'path'\n            else:\n                # 文件不存在\n                return 'text'\n        else:\n            return media\n\n    def add_media_to_materials(self,file):\n        \"\"\"\n            添加媒体到素材库\n\n            如果是DraftMaterial类直接添加到素材库\n\n            如果是文件路径先转化为DraftMaterial\n        \"\"\"\n        if type(file) == Material:\n            self.materials[file.file_Path] = file\n            self.draft_materials.append(file.data)\n            return file\n        if file not in self.materials:\n            mate = Material(file)\n            self.materials[file] = mate\n            self.draft_materials.append(mate.data)\n            return mate\n        else:\n            return self.materials[file]\n\n    def _content_material(self,material:Material):\n        materials = {}\n        extra_material_refs = []\n        if material.metetype == 'video':\n            materials['speeds']=template.speed()\n            materials['sound_channel_mappings']=template.sound_channel_mapping()\n            materials['canvases']=template.canvase()\n        elif material.metetype == 'photo':\n            pass\n        elif material.metetype == 'audio':\n            materials['speeds']=template.speed()\n            materials['sound_channel_mappings']=template.sound_channel_mapping()\n            materials['beats']=template.sound_channel_mapping()\n        elif material.metetype == 'text':\n            materials['material_animations']=template.material_animation()\n\n        materials[f'{material.track_type}s'] = material.content_material\n        \n        for key in materials:\n            extra_material_refs.append(materials[key]['id'])\n        return materials,extra_material_refs,material.content_material['id']\n\n    def add_media_to_track(self,media,start=0,duration=0,index=0):\n        if duration==0:\n            duration = media.duration\n\n        segment = template.segment()\n        track = []\n\n        if type(media) == str:\n            media = Material(media)\n\n        if media.metetype == 'video':\n            track = self.tracks.add_video_track(index)\n        elif media.metetype == 'photo':\n            track = self.tracks.add_video_track(index)\n        elif media.metetype == 'audio':\n            track = self.tracks.add_audio_track(index)\n        elif media.metetype == 'text':\n            track = self.tracks.add_text_track(index)\n\n        # 轨道总时长\n        track_duration = 0\n        if len(track['segments']) != 0:\n            track_duration = track['segments'][-1]['target_timerange']['duration'] + track['segments'][-1]['target_timerange']['start']\n\n        materials,extra_material_refs,material_id = self._content_material(media)\n        for key in materials:\n            self.content_materials[key].append(materials[key])\n\n        segment['extra_material_refs'] = extra_material_refs\n        segment['material_id'] = material_id\n        segment['source_timerange'] = { \"duration\": duration, \"start\": start }\n        segment['target_timerange'] = { \"duration\": duration, \"start\": track_duration }\n        \n        self.tracks.to_track(media.metetype,segment,index)\n\n    def save(self):\n        \"\"\"保存草稿\"\"\"\n        self.content['tracks'] = self.tracks._composite() # 覆盖轨道\n        util.write_json(self.content_path,self.content)\n        util.write_json(self.meta_info_path,self.meta_info)\n\nif __name__ == \"__main__\":\n    # 新建项目\n    draft = Draft(\"草稿\")\n    text = Material('Hello World')\n    text.change_color('#ABCABC')\n    draft.add_media_to_track(text,duration=3000000)\n    draft.save()"
  },
  {
    "path": "draft/material.py",
    "content": "import template\nimport os\nfrom pymediainfo import MediaInfo\n\nclass Material:\n\n    media_type_mapping = {\n        \"video\": \"video\",\n        \"audio\": \"music\"\n    }\n\n    def __init__(self, file) -> None:\n        self._data = template.material()\n        self.metetype = ''\n        self.track_type = ''\n        self.width = 0\n        self.height = 0\n        self.duration = 0\n        self.extra_info = ''\n        self.file_Path = ''\n        self.id = self._data['id']\n        self.content_material = {}\n\n        if type(file) == str:\n            if os.path.exists(file):\n                self.to_material(file)\n                if self.track_type == 'video':\n                    self.content_material = self.video()\n                elif self.track_type == 'audio':\n                    self.content_material =self.audio()\n            else:\n                self.metetype = 'text'\n                self.track_type = 'text'\n                self.content_material = self.text()\n                self.content_material['content'] = self.content_material['content'].replace('默认文本',file)\n        elif type(file) == dict:\n            # 估计用不到\n            self.load_material(file)\n        else:\n            pass # 素材类型错误\n\n    @property\n    def data(self):\n        self._data['metetype'] = self.metetype\n        self._data['width'] = self.width\n        self._data['height'] = self.height\n        self._data['duration'] = self.duration\n        self._data['extra_info'] = self.extra_info\n        self._data['file_Path'] = self.file_Path\n        return self._data\n    \n    @data.setter\n    def data(self, value):\n        self._data = value\n\n    def to_material(self,file_path):\n        \"\"\"\n            通过文件的方式去加载为素材\n        \"\"\"\n        media_info = MediaInfo.parse(file_path).to_data()[\"tracks\"][1]\n        self.track_type = media_info['track_type'].lower()\n        self.metetype = self.media_type_mapping[self.track_type]\n        if  \"width\" in media_info:\n            self.width = media_info['width']\n            self.height = media_info['height']\n        self.duration = media_info['duration']*1000\n        self.extra_info= file_path.split(\"/\")[-1]\n        self.file_Path = file_path\n\n    def load_material(self,material):\n        \"\"\"\n            将剪映的素材加载为素材类\n        \"\"\"\n        self.metetype =material['metetype']\n        self.width = material['width']\n        self.height = material['height']\n        self.duration = material['duration']\n        self.extra_info = material['extra_info']\n        self.file_Path =material['file_Path']\n        self.id = material['id']\n\n    def video(self):\n        v = template.video()\n        v[\"duration\"]= self.duration\n        v[\"height\"]= self.height\n        v[\"local_material_id\"]= self.id\n        v[\"material_name\"]=  self.extra_info\n        v[\"path\"]=  self.file_Path\n        v[\"type\"]=  self.metetype\n        v[\"width\"]=  self.width\n        return v\n    \n    def audio(self):\n        a = template.audio()\n        a[\"duration\"]= self.duration\n        a[\"local_material_id\"]= self.id        \n        a[\"name\"]=  self.extra_info\n        a[\"path\"]=  self.file_Path\n        a[\"type\"]=  \"extract_\" + self.metetype\n        return a\n    \n    def text(self):\n        t = template.text()\n        return t\n\n    def change_color(self,hex):\n        self.content_material['text_color'] = hex\n        r = int(hex[1:3],16)\n        g = int(hex[3:5],16)\n        b = int(hex[5:7],16)\n        color1 = \"<color=(1.000000, 1.000000, 1.000000, 1.000000)>\"\n        color2 = F'<color=({round(r/255,6):.6f}, {round(g/255,6):.6f}, {round(b/255,6):.6f}, 1.000000)>' \n        self.content_material['content'] = self.content_material['content'].replace(color1,color2)"
  },
  {
    "path": "draft/template.py",
    "content": "import time\nimport util\n\ndef canvase():\n    return {\n        \"album_image\": \"\",\n        \"blur\": 0.0,\n        \"color\": \"\",\n        \"id\": util.generate_id(),\n        \"image\": \"\",\n        \"image_id\": \"\",\n        \"image_name\": \"\",\n        \"source_platform\": 0,\n        \"team_id\": \"\",\n        \"type\": \"canvas_color\"\n    }\n\ndef sound_channel_mapping():\n    return {\n        \"audio_channel_mapping\": 0,\n        \"id\":  util.generate_id(),\n        \"is_config_open\": False,\n        \"type\": \"none\"\n    }\n\ndef speed():\n    return {\n        \"curve_speed\": None,\n        \"id\": util.generate_id(),\n        \"mode\": 0,\n        \"speed\": 1.0,\n        \"type\": \"speed\"\n    }\n\ndef video():\n    return {\n            \"audio_fade\": None,\n            \"cartoon_path\": \"\",\n            \"category_id\": \"\",\n            \"category_name\": \"local\",\n            \"check_flag\": 63487,\n            \"crop\": {\n            \"lower_left_x\": 0.0,\n            \"lower_left_y\": 1.0,\n            \"lower_right_x\": 1.0,\n            \"lower_right_y\": 1.0,\n            \"upper_left_x\": 0.0,\n            \"upper_left_y\": 0.0,\n            \"upper_right_x\": 1.0,\n            \"upper_right_y\": 0.0\n            },\n            \"crop_ratio\": \"free\",\n            \"crop_scale\": 1.0,\n            \"duration\": 0,\n            \"extra_type_option\": 0,\n            \"formula_id\": \"\",\n            \"freeze\": None,\n            \"gameplay\": None,\n            \"has_audio\": True,\n            \"height\": 0,\n            \"id\": util.generate_id(),\n            \"intensifies_audio_path\": \"\",\n            \"intensifies_path\": \"\",\n            \"is_unified_beauty_mode\": False,\n            \"local_id\": \"\",\n            \"local_material_id\":\"\",\n            \"material_id\": \"\",\n            \"material_name\": \"\",\n            \"material_url\": \"\",\n            \"matting\": {\n            \"flag\": 0,\n            \"has_use_quick_brush\": False,\n            \"has_use_quick_eraser\": False,\n            \"interactiveTime\": [],\n            \"path\": \"\",\n            \"strokes\": []\n            },\n            \"media_path\": \"\",\n            \"object_locked\": None,\n            \"path\": \"\",\n            \"picture_from\": \"none\",\n            \"picture_set_category_id\": \"\",\n            \"picture_set_category_name\": \"\",\n            \"request_id\": \"\",\n            \"reverse_intensifies_path\": \"\",\n            \"reverse_path\": \"\",\n            \"source_platform\": 0,\n            \"stable\": None,\n            \"team_id\": \"\",\n            \"type\":\"\",\n            \"video_algorithm\": {\n            \"algorithms\": [],\n            \"deflicker\": None,\n            \"motion_blur_config\": None,\n            \"noise_reduction\": None,\n            \"path\": \"\",\n            \"time_range\": None\n            },\n            \"width\": 0\n        }\n\ndef material():\n    return {\n        \"create_time\": int(time.time()),\n        \"duration\": 0,\n        \"extra_info\": \"\",\n        \"file_Path\": \"\",\n        \"height\": 0,\n        \"id\": util.generate_id(),\n        \"import_time\": int(time.time()),\n        \"import_time_ms\":  int(time.time())*10^6,\n        \"md5\": \"\",\n        \"metetype\": \"\",\n        \"roughcut_time_range\": { \"duration\": 0, \"start\": 0 },\n        \"sub_time_range\": { \"duration\": -1, \"start\": -1 },\n        \"type\": 0,\n        \"width\": 0\n    }\n\ndef track():\n    return {\n      \"attribute\": 0,\n      \"flag\": 0,\n      \"id\": util.generate_id(),\n      \"segments\":[],\n      \"type\": \"\"\n    }\n\ndef segment():\n    return {\n          \"cartoon\": False,\n          \"clip\": {\n            \"alpha\": 1.0,\n            \"flip\": { \"horizontal\": False, \"vertical\": False },\n            \"rotation\": 0.0,\n            \"scale\": { \"x\": 1.0, \"y\": 1.0 },\n            \"transform\": { \"x\": 0.0, \"y\": 0.0 }\n          },\n          \"common_keyframes\": [],\n          \"enable_adjust\": True,\n          \"enable_color_curves\": True,\n          \"enable_color_wheels\": True,\n          \"enable_lut\": True,\n          \"enable_smart_color_adjust\": False,\n          \"extra_material_refs\": [\n          ],\n          \"group_id\": \"\",\n          \"hdr_settings\": { \"intensity\": 1.0, \"mode\": 1, \"nits\": 1000 },\n          \"id\": util.generate_id(),\n          \"intensifies_audio\": False,\n          \"is_placeholder\": False,\n          \"is_tone_modify\": False,\n          \"keyframe_refs\": [],\n          \"last_nonzero_volume\": 1.0,\n          \"material_id\":\"\",\n          \"render_index\": 0,\n          \"reverse\": False,\n          \"source_timerange\": { \"duration\": 0, \"start\": 0 },\n          \"speed\": 1.0,\n          \"target_timerange\": { \"duration\": 0, \"start\": 0 },\n          \"template_id\": \"\",\n          \"template_scene\": \"default\",\n          \"track_attribute\": 0,\n          \"track_render_index\": 0,\n          \"visible\": True,\n          \"volume\": 1.0\n        }\n\ndef beat():\n    return {\n        \"ai_beats\": {\n          \"beats_path\": \"\",\n          \"beats_url\": \"\",\n          \"melody_path\": \"\",\n          \"melody_percents\": [0.0],\n          \"melody_url\": \"\"\n        },\n        \"enable_ai_beats\": False,\n        \"gear\": 404,\n        \"id\": util.generate_id(),\n        \"mode\": 404,\n        \"type\": \"beats\",\n        \"user_beats\": [],\n        \"user_delete_ai_beats\": None\n      }\n\ndef audio():\n    return {\n        \"app_id\": 0,\n        \"category_id\": \"\",\n        \"category_name\": \"local\",\n        \"check_flag\": 1,\n        \"duration\": 0,\n        \"effect_id\": \"\",\n        \"formula_id\": \"\",\n        \"id\": util.generate_id(),\n        \"intensifies_path\": \"\",\n        \"local_material_id\": \"\",\n        \"music_id\": util.generate_id(),\n        \"name\": \"Krubb Wenkroist - Bleach.mp3\",\n        \"path\": \"D:/Music/Krubb Wenkroist - Bleach.mp3\",\n        \"request_id\": \"\",\n        \"resource_id\": \"\",\n        \"source_platform\": 0,\n        \"team_id\": \"\",\n        \"text_id\": \"\",\n        \"tone_category_id\": \"\",\n        \"tone_category_name\": \"\",\n        \"tone_effect_id\": \"\",\n        \"tone_effect_name\": \"\",\n        \"tone_speaker\": \"\",\n        \"tone_type\": \"\",\n        \"type\": \"extract_music\",\n        \"video_id\": \"\",\n        \"wave_points\": []\n      }\n\ndef text():\n    return {\n        \"add_type\": 0,\n        \"alignment\": 1,\n        \"background_alpha\": 1.0,\n        \"background_color\": \"\",\n        \"background_height\": 1.0,\n        \"background_horizontal_offset\": 0.0,\n        \"background_round_radius\": 0.0,\n        \"background_style\": 0,\n        \"background_vertical_offset\": 0.0,\n        \"background_width\": 1.0,\n        \"bold_width\": 0.0,\n        \"border_color\": \"\",\n        \"border_width\": 0.08,\n        \"check_flag\": 7,\n        \"content\": \"<font id=\\\"\\\" path=\\\"E:/JianyingPro/4.2.0.10100/Resources/Font/SystemFont/zh-hans.ttf\\\"><color=(1.000000, 1.000000, 1.000000, 1.000000)><size=15.000000>[默认文本]</size></color></font>\",\n        \"font_category_id\": \"\",\n        \"font_category_name\": \"\",\n        \"font_id\": \"\",\n        \"font_name\": \"\",\n        \"font_path\": \"E:/JianyingPro/4.2.0.10100/Resources/Font/SystemFont/zh-hans.ttf\",\n        \"font_resource_id\": \"\",\n        \"font_size\": 15.0,\n        \"font_source_platform\": 0,\n        \"font_team_id\": \"\",\n        \"font_title\": \"none\",\n        \"font_url\": \"\",\n        \"fonts\": [],\n        \"force_apply_line_max_width\": False,\n        \"global_alpha\": 1.0,\n        \"group_id\": \"\",\n        \"has_shadow\": False,\n        \"id\": util.generate_id(),\n        \"initial_scale\": 1.0,\n        \"is_rich_text\": False,\n        \"italic_degree\": 0,\n        \"ktv_color\": \"\",\n        \"language\": \"\",\n        \"layer_weight\": 1,\n        \"letter_spacing\": 0.0,\n        \"line_spacing\": 0.02,\n        \"name\": \"\",\n        \"preset_category\": \"\",\n        \"preset_category_id\": \"\",\n        \"preset_has_set_alignment\": False,\n        \"preset_id\": \"\",\n        \"preset_index\": 0,\n        \"preset_name\": \"\",\n        \"recognize_type\": 0,\n        \"relevance_segment\": [],\n        \"shadow_alpha\": 0.8,\n        \"shadow_angle\": -45.0,\n        \"shadow_color\": \"\",\n        \"shadow_distance\": 8.0,\n        \"shadow_point\": { \"x\": 1.0182337649086284, \"y\": -1.0182337649086284 },\n        \"shadow_smoothing\": 1.0,\n        \"shape_clip_x\": False,\n        \"shape_clip_y\": False,\n        \"style_name\": \"\",\n        \"sub_type\": 0,\n        \"text_alpha\": 1.0,\n        \"text_color\": \"#FFFFFF\",\n        \"text_preset_resource_id\": \"\",\n        \"text_size\": 30,\n        \"text_to_audio_ids\": [],\n        \"tts_auto_update\": False,\n        \"type\": \"text\",\n        \"typesetting\": 0,\n        \"underline\": False,\n        \"underline_offset\": 0.22,\n        \"underline_width\": 0.05,\n        \"use_effect_default_color\": True,\n        \"words\": []\n      }\n\ndef material_animation():\n    return {\n        \"animations\": [],\n        \"id\": util.generate_id(),\n        \"type\": \"sticker_animation\"\n    }\n\n"
  },
  {
    "path": "draft/track.py",
    "content": "import template\n\nclass Tracks:\n\n    def __init__(self) -> None:\n        \"\"\"初始化轨道类型\n\n        Args:\n            tracks (list, optional): 轨道. Defaults to [].\n        \"\"\"\n        self.video_track = []\n        self.audio_track = []\n        self.text_track = []\n        \n    def add_video_track(self,track_index=0):\n        \"\"\"\n        添加一条视频轨道\n        当track_index超过现有轨道数时，则新建轨道，否则为选取轨道    \n        Args:\n            track_index (int, optional): 第几条轨道. Defaults to 0.\n\n        Returns:\n            dict: 返回轨道字典\n        \"\"\"\n        track = self._add_track(self.video_track,'video',track_index)\n        if track:\n            self.video_track.append(track)\n        return self.video_track[track_index]\n    \n    def add_audio_track(self,track_index=0):\n        track = self._add_track(self.audio_track,'audio',track_index)\n        if track:\n            # 当视频轨道为空时\n            if len(self.video_track) == 0 :\n                self.add_video_track()\n            self.audio_track.append(track)\n        return self.audio_track[track_index]\n\n    def add_text_track(self,track_index=0):\n        track = self._add_track(self.text_track,'text',track_index)\n        if track:\n            # 当视频轨道为空时\n            if len(self.video_track) == 0 :\n                self.add_video_track()\n            self.text_track.append(track)\n        return self.text_track[track_index]\n\n    def _add_track(self,tracks,track_type,track_index):\n        track_len = len(tracks)\n        if track_index == track_len:\n            track = template.track()\n            track['type'] = track_type\n            if track_len:\n                track['flag'] = 2\n            return track\n        else:\n            return False\n\n    def to_track(self,metetype,segment,track_index):\n        if metetype == \"video\":\n            self.video_track[track_index]['segments'].append(segment)\n        elif metetype == \"music\":\n            self.audio_track[track_index]['segments'].append(segment)\n        elif metetype == \"text\":\n            self.text_track[track_index]['segments'].append(segment)\n\n    def _composite(self):\n        \"\"\"\n            将所有轨道合成为一个列表\n        \"\"\"\n        track = []\n        track.extend(self.video_track)\n        track.extend(self.text_track)\n        track.extend(self.audio_track)\n        return track\n    "
  },
  {
    "path": "draft/util.py",
    "content": "import os\nimport shutil\nimport uuid\nimport json\n\ndef generate_id():\n    \"\"\"\n        生成uuid\n    \"\"\"\n    return str(uuid.uuid4()).upper()\n\ndef write_json(path,data):\n    with open(path,'w') as file:\n        json.dump(data,file)\n\ndef read_json(path):\n    with open(path,'r') as file:\n        return json.load(file)\n\ndef new_folder(folder_path):\n    if os.path.exists(folder_path):\n        for filename in os.listdir(folder_path):\n            file_path = os.path.join(folder_path, filename)\n            if os.path.isfile(file_path):\n                os.remove(file_path)\n            elif os.path.isdir(file_path):\n                shutil.rmtree(file_path)\n    else:\n        os.mkdir(folder_path)\n"
  },
  {
    "path": "main.py",
    "content": ""
  },
  {
    "path": "project.py",
    "content": "import random\nimport os\nimport selenium\nfrom draft import Draft\nfrom draft import Material\n\n# 新建项目\ndraft = Draft(\"测试草稿\")\n# 选择背景音乐并添加鼓点\naudio = \"D:/Music/Krubb Wenkroist - Bleach.mp3\"\ndraft.add_media_to_track(audio)\n# 读取鼓点\nbeats = draft.content_materials['beats'][0]['user_beats']\n# 加载视频\nfiles= []\nfor pt in os.listdir('D:/myCode/Python/spider/douyin_spider/media/video/小仙儿'):\n    file_path = os.path.join('D:/myCode/Python/spider/douyin_spider/media/video/小仙儿',pt)\n    files.append(file_path)\n# 随机裁切视频为合适时长\nend = 0\nfor beat in beats:\n    duration = beat - end\n    mate = Material(files[random.randint(0,len(files)-1)])\n    start = int(random.uniform(0,(mate.duration-duration)/1000))*1000\n    Draft.add_media_to_track(mate,start,duration)\n    end = beat\n# 保存草稿\ndraft.save()"
  },
  {
    "path": "template json/draft_content.json",
    "content": "{\n  \"canvas_config\": { \"height\": 1080, \"ratio\": \"original\", \"width\": 1920 },\n  \"color_space\": -1,\n  \"config\": {\n    \"adjust_max_index\": 1,\n    \"attachment_info\": [],\n    \"combination_max_index\": 1,\n    \"export_range\": null,\n    \"extract_audio_last_index\": 1,\n    \"lyrics_recognition_id\": \"\",\n    \"lyrics_sync\": true,\n    \"lyrics_taskinfo\": [],\n    \"maintrack_adsorb\": true,\n    \"material_save_mode\": 0,\n    \"original_sound_last_index\": 1,\n    \"record_audio_last_index\": 1,\n    \"sticker_max_index\": 1,\n    \"subtitle_recognition_id\": \"\",\n    \"subtitle_sync\": true,\n    \"subtitle_taskinfo\": [],\n    \"system_font_list\": [],\n    \"video_mute\": false,\n    \"zoom_info_params\": null\n  },\n  \"cover\": null,\n  \"create_time\": 0,\n  \"duration\": 0,\n  \"extra_info\": null,\n  \"fps\": 30.0,\n  \"free_render_index_mode_on\": false,\n  \"group_container\": null,\n  \"id\": \"\",\n  \"keyframe_graph_list\": [],\n  \"keyframes\": {\n    \"adjusts\": [],\n    \"audios\": [],\n    \"effects\": [],\n    \"filters\": [],\n    \"handwrites\": [],\n    \"stickers\": [],\n    \"texts\": [],\n    \"videos\": []\n  },\n  \"last_modified_platform\": {\n    \"app_id\": 3704,\n    \"app_source\": \"lv\",\n    \"app_version\": \"4.2.0\",\n    \"device_id\": \"9af4f431929e062ff217b53f97a4956c\",\n    \"hard_disk_id\": \"547aa5351496e9d49dfb385244653b24\",\n    \"mac_address\": \"d9aed9fab7102f4f36bd91f70f9b043e,10d2717e7d06a80d409fbb21516ebec0,c0f126ec1d37ccfde3369bf4bad2252f,0e7757ff99bacd8f6e70dd46dbdfbb04\",\n    \"os\": \"windows\",\n    \"os_version\": \"10.0.25381\"\n  },\n  \"materials\": {\n    \"audio_balances\": [],\n    \"audio_effects\": [],\n    \"audio_fades\": [],\n    \"audios\": [],\n    \"beats\": [],\n    \"canvases\": [],\n    \"chromas\": [],\n    \"color_curves\": [],\n    \"drafts\": [],\n    \"effects\": [],\n    \"green_screens\": [],\n    \"handwrites\": [],\n    \"hsl\": [],\n    \"images\": [],\n    \"log_color_wheels\": [],\n    \"manual_deformations\": [],\n    \"masks\": [],\n    \"material_animations\": [],\n    \"placeholders\": [],\n    \"plugin_effects\": [],\n    \"primary_color_wheels\": [],\n    \"realtime_denoises\": [],\n    \"sound_channel_mappings\": [],\n    \"speeds\": [],\n    \"stickers\": [],\n    \"tail_leaders\": [],\n    \"text_templates\": [],\n    \"texts\": [],\n    \"transitions\": [],\n    \"video_effects\": [],\n    \"video_trackings\": [],\n    \"videos\": []\n  },\n  \"mutable_config\": null,\n  \"name\": \"\",\n  \"new_version\": \"75.0.0\",\n  \"platform\": {\n    \"app_id\": 3704,\n    \"app_source\": \"lv\",\n    \"app_version\": \"4.2.0\",\n    \"device_id\": \"9af4f431929e062ff217b53f97a4956c\",\n    \"hard_disk_id\": \"547aa5351496e9d49dfb385244653b24\",\n    \"mac_address\": \"d9aed9fab7102f4f36bd91f70f9b043e,10d2717e7d06a80d409fbb21516ebec0,c0f126ec1d37ccfde3369bf4bad2252f,0e7757ff99bacd8f6e70dd46dbdfbb04\",\n    \"os\": \"windows\",\n    \"os_version\": \"10.0.25381\"\n  },\n  \"relationships\": [],\n  \"render_index_track_mode_on\": false,\n  \"retouch_cover\": null,\n  \"source\": \"default\",\n  \"static_cover_image_path\": \"\",\n  \"tracks\": [],\n  \"update_time\": 0,\n  \"version\": 360000\n}\n"
  },
  {
    "path": "template json/draft_meta_info.json",
    "content": "{\n  \"draft_cloud_capcut_purchase_info\": \"\",\n  \"draft_cloud_last_action_download\": false,\n  \"draft_cloud_purchase_info\": \"\",\n  \"draft_cloud_template_id\": \"\",\n  \"draft_cloud_tutorial_info\": \"\",\n  \"draft_cloud_videocut_purchase_info\": \"\",\n  \"draft_cover\": \"\",\n  \"draft_deeplink_url\": \"\",\n  \"draft_enterprise_info\": {\n    \"draft_enterprise_extra\": \"\",\n    \"draft_enterprise_id\": \"\",\n    \"draft_enterprise_name\": \"\"\n  },\n  \"draft_fold_path\": \"\",\n  \"draft_id\": \"\",\n  \"draft_is_article_video_draft\": false,\n  \"draft_is_from_deeplink\": \"false\",\n  \"draft_materials\": [\n    { \"type\": 0, \"value\": [] },\n    { \"type\": 1, \"value\": [] },\n    { \"type\": 2, \"value\": [] },\n    { \"type\": 3, \"value\": [] },\n    { \"type\": 6, \"value\": [] },\n    { \"type\": 7, \"value\": [] },\n    { \"type\": 8, \"value\": [] }\n  ],\n  \"draft_materials_copied_info\": [],\n  \"draft_name\": \"\",\n  \"draft_new_version\": \"\",\n  \"draft_removable_storage_device\": \"\",\n  \"draft_root_path\": \"\",\n  \"draft_segment_extra_info\": [],\n  \"draft_timeline_materials_size_\": 0,\n  \"tm_draft_cloud_completed\": \"\",\n  \"tm_draft_cloud_modified\": 0,\n  \"tm_draft_create\": 0,\n  \"tm_draft_modified\": 0,\n  \"tm_duration\": 0\n}\n"
  },
  {
    "path": "test.py",
    "content": "from draft import Draft\n\nprint(Draft)"
  }
]