import json import zipfile import os import shutil from typing import Optional, Dict, Any class JianYingConverter: def __init__(self, input_cmp: str, output_cmp: str, target_version: str = "4.0"): """ 初始化剪映草稿转换器 :param input_cmp: 输入高版本.cmp草稿路径 :param output_cmp: 输出低版本.cmp草稿路径 :param target_version: 目标低版本(支持 "3.0" / "4.0") """ self.input_cmp = input_cmp self.output_cmp = output_cmp self.target_version = target_version self.temp_dir = "temp_jianying" # 临时解压目录 self.core_json = "project.json" # 核心配置文件 def _unzip_cmp(self) -> bool: """解压.cmp文件到临时目录""" if not os.path.exists(self.input_cmp): print(f"错误:输入文件不存在 → {self.input_cmp}") return False # 清空并创建临时目录 if os.path.exists(self.temp_dir): shutil.rmtree(self.temp_dir) os.makedirs(self.temp_dir) try: with zipfile.ZipFile(self.input_cmp, 'r') as zip_ref: zip_ref.extractall(self.temp_dir) print(f"成功解压到临时目录 → {self.temp_dir}") return True except Exception as e: print(f"解压失败:{str(e)}") return False def _load_project_json(self) -> Optional[Dict[str, Any]]: """读取project.json""" json_path = os.path.join(self.temp_dir, self.core_json) if not os.path.exists(json_path): print(f"错误:未找到核心配置文件 → {json_path}") return None try: with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) print("成功读取project.json") return data except json.JSONDecodeError: # 部分高版本草稿可能有简单加密(如Base64),尝试解密 try: import base64 with open(json_path, 'r', encoding='utf-8') as f: encrypted = f.read() decrypted = base64.b64decode(encrypted).decode('utf-8') data = json.loads(decrypted) print("成功解密并读取project.json") return data except Exception as e: print(f"读取/解密JSON失败:{str(e)}") return None def _downgrade_json(self, data: Dict[str, Any]) -> Dict[str, Any]: """根据目标版本降级JSON结构""" print(f"正在降级到剪映 v{self.target_version} 格式...") # 1. 移除高版本新增的顶层字段(基于逆向分析) high_version_fields = [ "meta_info", "plugin_version", "advanced_settings", "ai_editor_info", "cloud_project_info", "multi_track_info" ] for field in high_version_fields: data.pop(field, None) # 2. 降级项目配置(强制设置兼容版本号) if "project_config" in data: if self.target_version == "3.0": data["project_config"]["version"] = "3.9.0" data["project_config"]["compatible_version"] = "3.0.0" else: # 4.0 data["project_config"]["version"] = "4.9.0" data["project_config"]["compatible_version"] = "4.0.0" # 3. 处理轨道数据(移除高版本特效/转场) if "tracks" in data: self._downgrade_tracks(data["tracks"]) # 4. 处理资源列表(移除云资源引用) if "resources" in data: data["resources"] = [res for res in data["resources"] if not res.get("is_cloud", False)] print("JSON降级完成") return data def _downgrade_tracks(self, tracks: list): """降级轨道数据(移除低版本不支持的效果)""" for track in tracks: if "clips" not in track: continue for clip in track["clips"]: # 移除高版本转场(保留基础转场) if "transition" in clip: transition_type = clip["transition"].get("type", "") # 低版本支持的基础转场列表(可根据需求扩展) supported_transitions = ["none", "fade", "slide", "push", "zoom"] if transition_type not in supported_transitions: clip["transition"] = {"type": "none", "duration": 0.3} # 移除高版本特效(如AI特效、高级滤镜) if "effects" in clip: clip["effects"] = [ eff for eff in clip["effects"] if eff.get("type") in ["filter", "adjust", "text", "sticker"] # 保留基础效果 ] # 降级音频效果(移除3D音效等高级功能) if "audio_effects" in clip: clip["audio_effects"] = [eff for eff in clip["audio_effects"] if eff.get("type") == "volume"] def _save_project_json(self, data: Dict[str, Any]) -> bool: """保存降级后的JSON到临时目录""" json_path = os.path.join(self.temp_dir, self.core_json) try: with open(json_path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) print("成功保存降级后的project.json") return True except Exception as e: print(f"保存JSON失败:{str(e)}") return False def _zip_to_cmp(self) -> bool: """将临时目录重新压缩为.cmp文件""" try: with zipfile.ZipFile(self.output_cmp, 'w', zipfile.ZIP_DEFLATED) as zip_ref: # 遍历临时目录所有文件,添加到压缩包 for root, dirs, files in os.walk(self.temp_dir): for file in files: file_path = os.path.join(root, file) # 保持压缩包内的相对路径 arcname = os.path.relpath(file_path, self.temp_dir) zip_ref.write(file_path, arcname) print(f"成功生成低版本草稿 → {self.output_cmp}") return True except Exception as e: print(f"压缩失败:{str(e)}") return False def clean_temp(self): """清理临时目录""" if os.path.exists(self.temp_dir): shutil.rmtree(self.temp_dir) print("临时目录已清理") def convert(self) -> bool: """执行完整转换流程""" print("=" * 50) print("剪映草稿高版本转低版本工具") print(f"输入:{self.input_cmp}") print(f"输出:{self.output_cmp}") print(f"目标版本:v{self.target_version}") print("=" * 50) try: # 1. 解压 if not self._unzip_cmp(): return False # 2. 读取JSON data = self._load_project_json() if not data: return False # 3. 降级JSON downgraded_data = self._downgrade_json(data) # 4. 保存JSON if not self._save_project_json(downgraded_data): return False # 5. 重新压缩 if not self._zip_to_cmp(): return False # 6. 清理临时文件 self.clean_temp() print("=" * 50) print("转换成功!请用目标版本剪映打开输出文件") print("注意:复杂特效/AI功能可能已降级为基础效果") print("=" * 50) return True except Exception as e: print(f"转换异常:{str(e)}") self.clean_temp() return False # ------------------------------ # 用法示例 # ------------------------------ if __name__ == "__main__": # 请修改以下参数 INPUT_CMP = "high_version_project.cmp" # 高版本草稿路径 OUTPUT_CMP = "low_version_project.cmp" # 输出低版本草稿路径 TARGET_VERSION = "4.0" # 目标低版本(3.0 或 4.0) # 创建转换器并执行 converter = JianYingConverter( input_cmp=INPUT_CMP, output_cmp=OUTPUT_CMP, target_version=TARGET_VERSION ) converter.convert()