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