mirror of
https://github.com/luoluoluo22/jianying-editor-skill.git
synced 2026-09-19 07:30:30 +08:00
Fix draft content file compatibility
This commit is contained in:
@@ -2,16 +2,17 @@ import argparse
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict
|
||||
|
||||
from utils.cli_protocol import emit_result, make_result
|
||||
from utils.config import CONFIG
|
||||
from utils.errors import InfraError
|
||||
from utils.formatters import find_draft_content_path
|
||||
from utils.logging_utils import setup_logger
|
||||
|
||||
logger = setup_logger("build_cloud_music_library")
|
||||
|
||||
import sys
|
||||
|
||||
def _get_default_projects_root() -> str:
|
||||
"""跨平台探测剪映草稿目录"""
|
||||
@@ -19,25 +20,44 @@ def _get_default_projects_root() -> str:
|
||||
home = os.path.expanduser("~")
|
||||
candidates = [
|
||||
os.path.join(home, "Movies", "JianyingPro Drafts"),
|
||||
os.path.join(home, "Movies", "JianyingPro", "User Data", "Projects", "com.lveditor.draft"),
|
||||
os.path.join(
|
||||
home, "Library", "Containers", "com.lemon.lvpro", "Data",
|
||||
"Library", "Application Support", "JianyingPro",
|
||||
"User Data", "Projects", "com.lveditor.draft",
|
||||
home, "Movies", "JianyingPro", "User Data", "Projects", "com.lveditor.draft"
|
||||
),
|
||||
os.path.join(
|
||||
home,
|
||||
"Library",
|
||||
"Containers",
|
||||
"com.lemon.lvpro",
|
||||
"Data",
|
||||
"Library",
|
||||
"Application Support",
|
||||
"JianyingPro",
|
||||
"User Data",
|
||||
"Projects",
|
||||
"com.lveditor.draft",
|
||||
),
|
||||
]
|
||||
else:
|
||||
local_app_data = os.getenv("LOCALAPPDATA", "")
|
||||
candidates = [
|
||||
os.path.join(local_app_data, "JianyingPro", "User Data", "Projects", "com.lveditor.draft"),
|
||||
os.path.join(local_app_data, "CapCut", "User Data", "Projects", "com.lveditor.draft"),
|
||||
] if local_app_data else []
|
||||
candidates = (
|
||||
[
|
||||
os.path.join(
|
||||
local_app_data, "JianyingPro", "User Data", "Projects", "com.lveditor.draft"
|
||||
),
|
||||
os.path.join(
|
||||
local_app_data, "CapCut", "User Data", "Projects", "com.lveditor.draft"
|
||||
),
|
||||
]
|
||||
if local_app_data
|
||||
else []
|
||||
)
|
||||
|
||||
for p in candidates:
|
||||
if os.path.exists(p):
|
||||
return p
|
||||
return candidates[0] if candidates else ""
|
||||
|
||||
|
||||
PROJECTS_ROOT = CONFIG.projects_root_override or _get_default_projects_root()
|
||||
|
||||
SKILL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -116,8 +136,8 @@ def build_libraries(
|
||||
project_path = os.path.join(projects_root, project_name)
|
||||
if not os.path.isdir(project_path):
|
||||
continue
|
||||
content_path = os.path.join(project_path, "draft_content.json")
|
||||
if not os.path.exists(content_path):
|
||||
content_path = find_draft_content_path(project_path)
|
||||
if not content_path:
|
||||
continue
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,58 +1,78 @@
|
||||
import os
|
||||
import json
|
||||
import csv
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from utils.formatters import find_draft_content_path
|
||||
|
||||
|
||||
# 路径定义 (跨平台)
|
||||
def _get_default_projects_root() -> str:
|
||||
if sys.platform == "darwin":
|
||||
home = os.path.expanduser("~")
|
||||
candidates = [
|
||||
os.path.join(home, "Movies", "JianyingPro Drafts"),
|
||||
os.path.join(home, "Movies", "JianyingPro", "User Data", "Projects", "com.lveditor.draft"),
|
||||
os.path.join(
|
||||
home, "Library", "Containers", "com.lemon.lvpro", "Data",
|
||||
"Library", "Application Support", "JianyingPro",
|
||||
"User Data", "Projects", "com.lveditor.draft",
|
||||
home, "Movies", "JianyingPro", "User Data", "Projects", "com.lveditor.draft"
|
||||
),
|
||||
os.path.join(
|
||||
home,
|
||||
"Library",
|
||||
"Containers",
|
||||
"com.lemon.lvpro",
|
||||
"Data",
|
||||
"Library",
|
||||
"Application Support",
|
||||
"JianyingPro",
|
||||
"User Data",
|
||||
"Projects",
|
||||
"com.lveditor.draft",
|
||||
),
|
||||
]
|
||||
else:
|
||||
local_app_data = os.getenv("LOCALAPPDATA", "")
|
||||
candidates = [
|
||||
os.path.join(local_app_data, "JianyingPro", "User Data", "Projects", "com.lveditor.draft"),
|
||||
] if local_app_data else []
|
||||
candidates = (
|
||||
[
|
||||
os.path.join(
|
||||
local_app_data, "JianyingPro", "User Data", "Projects", "com.lveditor.draft"
|
||||
),
|
||||
]
|
||||
if local_app_data
|
||||
else []
|
||||
)
|
||||
|
||||
for p in candidates:
|
||||
if os.path.exists(p):
|
||||
return p
|
||||
return candidates[0] if candidates else ""
|
||||
|
||||
|
||||
PROJECTS_ROOT = _get_default_projects_root()
|
||||
|
||||
# Skill 根目录
|
||||
SKILL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
CSV_FILE = os.path.join(SKILL_ROOT, "data", "cloud_text_styles.csv")
|
||||
|
||||
|
||||
def build_text_styles_library():
|
||||
print(f"🔍 Scanning Jianying projects for styled text (Flower Text)...")
|
||||
|
||||
print("🔍 Scanning Jianying projects for styled text (Flower Text)...")
|
||||
|
||||
# 1. 尝试增量读取现有的库
|
||||
style_library = {} # style_id -> {style_id, name_hint, categories (set)}
|
||||
|
||||
style_library = {} # style_id -> {style_id, name_hint, categories (set)}
|
||||
|
||||
if os.path.exists(CSV_FILE):
|
||||
try:
|
||||
with open(CSV_FILE, 'r', encoding='utf-8', newline='') as f:
|
||||
lines = [l for l in f.readlines() if not l.startswith("#")]
|
||||
with open(CSV_FILE, "r", encoding="utf-8", newline="") as f:
|
||||
lines = [line for line in f.readlines() if not line.startswith("#")]
|
||||
if lines:
|
||||
reader = csv.DictReader(lines)
|
||||
for row in reader:
|
||||
s_id = row['style_id']
|
||||
cats = set(row['categories'].split('|')) if row['categories'] else set()
|
||||
s_id = row["style_id"]
|
||||
cats = set(row["categories"].split("|")) if row["categories"] else set()
|
||||
style_library[s_id] = {
|
||||
"style_id": s_id,
|
||||
"name_hint": row['name_hint'],
|
||||
"categories": cats
|
||||
"name_hint": row["name_hint"],
|
||||
"categories": cats,
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"⚠️ Reading existing CSV failed: {e}")
|
||||
@@ -63,42 +83,47 @@ def build_text_styles_library():
|
||||
project_path = os.path.join(PROJECTS_ROOT, project_name)
|
||||
if not os.path.isdir(project_path):
|
||||
continue
|
||||
|
||||
content_path = os.path.join(project_path, "draft_content.json")
|
||||
if not os.path.exists(content_path):
|
||||
|
||||
content_path = find_draft_content_path(project_path)
|
||||
if not content_path:
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
with open(content_path, 'r', encoding='utf-8') as f:
|
||||
with open(content_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
texts = data.get('materials', {}).get('texts', [])
|
||||
texts = data.get("materials", {}).get("texts", [])
|
||||
for t in texts:
|
||||
content = t.get('content')
|
||||
if not content: continue
|
||||
|
||||
content = t.get("content")
|
||||
if not content:
|
||||
continue
|
||||
|
||||
try:
|
||||
content_json = json.loads(content)
|
||||
styles = content_json.get('styles', [])
|
||||
styles = content_json.get("styles", [])
|
||||
for sty in styles:
|
||||
eff = sty.get('effectStyle', {})
|
||||
s_id = eff.get('id')
|
||||
if not s_id: continue
|
||||
|
||||
eff = sty.get("effectStyle", {})
|
||||
s_id = eff.get("id")
|
||||
if not s_id:
|
||||
continue
|
||||
|
||||
# 使用文本内容作为提示,除非是“默认文本”
|
||||
raw_text = content_json.get('text', '')
|
||||
raw_text = content_json.get("text", "")
|
||||
hint = raw_text if raw_text != "默认文本" else "Flower Style"
|
||||
|
||||
|
||||
if s_id not in style_library:
|
||||
style_library[s_id] = {
|
||||
"style_id": s_id,
|
||||
"name_hint": hint,
|
||||
"categories": {project_name} # 使用工程名作为初期分类
|
||||
"categories": {project_name}, # 使用工程名作为初期分类
|
||||
}
|
||||
else:
|
||||
if hint != "Flower Style" and style_library[s_id]["name_hint"] == "Flower Style":
|
||||
if (
|
||||
hint != "Flower Style"
|
||||
and style_library[s_id]["name_hint"] == "Flower Style"
|
||||
):
|
||||
style_library[s_id]["name_hint"] = hint
|
||||
style_library[s_id]["categories"].add(project_name)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"⚠ Skipping project '{project_name}': {e}")
|
||||
@@ -108,24 +133,29 @@ def build_text_styles_library():
|
||||
# 4. 写入 CSV
|
||||
os.makedirs(os.path.dirname(CSV_FILE), exist_ok=True)
|
||||
sorted_ids = sorted(style_library.keys())
|
||||
|
||||
with open(CSV_FILE, 'w', encoding='utf-8', newline='') as f:
|
||||
|
||||
with open(CSV_FILE, "w", encoding="utf-8", newline="") as f:
|
||||
f.write("# JianYing Cloud Text Styles Library (Flower Text IDs Scanned from Projects)\n")
|
||||
f.write("# AI Guidance: Use 'style_id' in add_styled_text(). If matching name found, use ID.\n")
|
||||
f.write(
|
||||
"# AI Guidance: Use 'style_id' in add_styled_text(). If matching name found, use ID.\n"
|
||||
)
|
||||
f.write("# Schema: style_id,name_hint,categories\n")
|
||||
|
||||
|
||||
writer = csv.DictWriter(f, fieldnames=["style_id", "name_hint", "categories"])
|
||||
writer.writeheader()
|
||||
for s_id in sorted_ids:
|
||||
info = style_library[s_id]
|
||||
writer.writerow({
|
||||
"style_id": s_id,
|
||||
"name_hint": info["name_hint"],
|
||||
"categories": "|".join(sorted(list(info["categories"])))
|
||||
})
|
||||
writer.writerow(
|
||||
{
|
||||
"style_id": s_id,
|
||||
"name_hint": info["name_hint"],
|
||||
"categories": "|".join(sorted(list(info["categories"]))),
|
||||
}
|
||||
)
|
||||
|
||||
print(f"✅ Success! Text Styles Library updated with {len(style_library)} entries.")
|
||||
print(f"📂 Saved to: {CSV_FILE}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build_text_styles_library()
|
||||
|
||||
@@ -3,7 +3,12 @@ import json
|
||||
import os
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from utils.formatters import get_all_drafts, get_default_drafts_root
|
||||
from utils.formatters import (
|
||||
DRAFT_CONTENT_FILENAMES,
|
||||
find_draft_content_path,
|
||||
get_all_drafts,
|
||||
get_default_drafts_root,
|
||||
)
|
||||
|
||||
|
||||
def _ok(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -50,15 +55,16 @@ def cmd_show(root: str, name: Optional[str], path: Optional[str], kind: str) ->
|
||||
draft_path = found["path"]
|
||||
draft_name = found["name"]
|
||||
|
||||
content_path = os.path.join(draft_path, "draft_content.json")
|
||||
content_path = find_draft_content_path(draft_path)
|
||||
meta_path = os.path.join(draft_path, "draft_meta_info.json")
|
||||
|
||||
data: Dict[str, Any] = {"name": draft_name, "path": draft_path}
|
||||
|
||||
try:
|
||||
if kind in {"content", "both"}:
|
||||
if not os.path.exists(content_path):
|
||||
return _err("not_found", f"Missing draft_content.json: {content_path}")
|
||||
if not content_path:
|
||||
expected = " or ".join(DRAFT_CONTENT_FILENAMES)
|
||||
return _err("not_found", f"Missing {expected}: {draft_path}")
|
||||
data["content"] = _read_json(content_path)
|
||||
|
||||
if kind in {"meta", "both"}:
|
||||
@@ -164,7 +170,9 @@ def main() -> int:
|
||||
p_summary = sub.add_parser("summary", help="Show compact draft summary")
|
||||
p_summary.add_argument("--name", help="Draft name")
|
||||
p_summary.add_argument("--path", help="Draft absolute path")
|
||||
p_summary.add_argument("--json", action="store_true", help="Print machine-readable JSON response")
|
||||
p_summary.add_argument(
|
||||
"--json", action="store_true", help="Print machine-readable JSON response"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
root = os.path.abspath(args.root)
|
||||
|
||||
+57
-14
@@ -3,7 +3,18 @@ import functools
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Dict, List, Union
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
DRAFT_CONTENT_FILENAMES = ("draft_info.json", "draft_content.json")
|
||||
|
||||
|
||||
def find_draft_content_path(draft_path: str) -> Optional[str]:
|
||||
"""Return the current draft content JSON path, supporting v5.9+ and legacy drafts."""
|
||||
for filename in DRAFT_CONTENT_FILENAMES:
|
||||
content_path = os.path.join(draft_path, filename)
|
||||
if os.path.exists(content_path):
|
||||
return content_path
|
||||
return None
|
||||
|
||||
|
||||
# ----------------- 路径自动探测 -----------------
|
||||
@@ -19,15 +30,30 @@ def get_default_drafts_root() -> str:
|
||||
candidates.extend(
|
||||
[
|
||||
os.path.join(home, "Movies", "JianyingPro Drafts"),
|
||||
os.path.join(home, "Movies", "JianyingPro", "User Data", "Projects", "com.lveditor.draft"),
|
||||
os.path.join(
|
||||
home, "Library", "Containers", "com.lemon.lvpro", "Data",
|
||||
"Library", "Application Support", "JianyingPro",
|
||||
"User Data", "Projects", "com.lveditor.draft",
|
||||
home, "Movies", "JianyingPro", "User Data", "Projects", "com.lveditor.draft"
|
||||
),
|
||||
os.path.join(
|
||||
home, "Library", "Application Support", "JianyingPro",
|
||||
"User Data", "Projects", "com.lveditor.draft",
|
||||
home,
|
||||
"Library",
|
||||
"Containers",
|
||||
"com.lemon.lvpro",
|
||||
"Data",
|
||||
"Library",
|
||||
"Application Support",
|
||||
"JianyingPro",
|
||||
"User Data",
|
||||
"Projects",
|
||||
"com.lveditor.draft",
|
||||
),
|
||||
os.path.join(
|
||||
home,
|
||||
"Library",
|
||||
"Application Support",
|
||||
"JianyingPro",
|
||||
"User Data",
|
||||
"Projects",
|
||||
"com.lveditor.draft",
|
||||
),
|
||||
]
|
||||
)
|
||||
@@ -40,22 +66,39 @@ def get_default_drafts_root() -> str:
|
||||
if local_app_data:
|
||||
candidates.extend(
|
||||
[
|
||||
os.path.join(local_app_data, "JianyingPro", "User Data", "Projects", "com.lveditor.draft"),
|
||||
os.path.join(local_app_data, "CapCut", "User Data", "Projects", "com.lveditor.draft"),
|
||||
os.path.join(
|
||||
local_app_data, "JianyingPro", "User Data", "Projects", "com.lveditor.draft"
|
||||
),
|
||||
os.path.join(
|
||||
local_app_data, "CapCut", "User Data", "Projects", "com.lveditor.draft"
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
if user_profile:
|
||||
candidates.append(
|
||||
os.path.join(
|
||||
user_profile, "AppData", "Local", "JianyingPro",
|
||||
"User Data", "Projects", "com.lveditor.draft",
|
||||
user_profile,
|
||||
"AppData",
|
||||
"Local",
|
||||
"JianyingPro",
|
||||
"User Data",
|
||||
"Projects",
|
||||
"com.lveditor.draft",
|
||||
)
|
||||
)
|
||||
|
||||
fallback = os.path.join(
|
||||
"C:", os.sep, "Users", "Administrator", "AppData", "Local",
|
||||
"JianyingPro", "User Data", "Projects", "com.lveditor.draft",
|
||||
"C:",
|
||||
os.sep,
|
||||
"Users",
|
||||
"Administrator",
|
||||
"AppData",
|
||||
"Local",
|
||||
"JianyingPro",
|
||||
"User Data",
|
||||
"Projects",
|
||||
"com.lveditor.draft",
|
||||
)
|
||||
|
||||
for path in candidates:
|
||||
@@ -74,7 +117,7 @@ def get_all_drafts(root_path: str = None) -> List[Dict]:
|
||||
for item in os.listdir(root):
|
||||
path = os.path.join(root, item)
|
||||
if os.path.isdir(path):
|
||||
if os.path.exists(os.path.join(path, "draft_content.json")) or os.path.exists(
|
||||
if find_draft_content_path(path) or os.path.exists(
|
||||
os.path.join(path, "draft_meta_info.json")
|
||||
):
|
||||
drafts.append({"name": item, "mtime": os.path.getmtime(path), "path": path})
|
||||
|
||||
+17
-1
@@ -15,12 +15,12 @@ if scripts_path not in sys.path:
|
||||
|
||||
from cloud_manager import CloudManager
|
||||
from core.mocking_ops import MockAudioMaterial
|
||||
from draft_inspector import cmd_summary
|
||||
from jy_wrapper import JyProject, draft
|
||||
from utils.formatters import safe_tim
|
||||
|
||||
|
||||
class TestJyWrapper(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# 使用临时目录作为测试环境
|
||||
@@ -166,6 +166,22 @@ class TestJyWrapper(unittest.TestCase):
|
||||
self.assertEqual(len(p.script.tracks["AudioTrack"].segments), 1)
|
||||
self.assertEqual(len(p.script.tracks["AudioTrack_1"].segments), 1)
|
||||
|
||||
def test_12_draft_inspector_reads_draft_info(self):
|
||||
"""测试 draft_inspector 兼容 v5.9+ 的 draft_info.json"""
|
||||
p = JyProject("TestInspectorInfo", drafts_root=self.test_output, overwrite=True)
|
||||
p.add_text_simple("Hello", "0s", "1s")
|
||||
p.save()
|
||||
|
||||
res = cmd_summary(
|
||||
root=self.test_output,
|
||||
name=None,
|
||||
path=os.path.join(self.test_output, "TestInspectorInfo"),
|
||||
)
|
||||
|
||||
self.assertTrue(res["ok"], res.get("reason"))
|
||||
self.assertEqual(res["data"]["name"], "TestInspectorInfo")
|
||||
self.assertGreaterEqual(res["data"]["track_count"], 1)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# 清理测试产物
|
||||
|
||||
Reference in New Issue
Block a user