Fix draft content file compatibility

This commit is contained in:
luoluoluo22
2026-06-12 09:32:35 +08:00
parent cb826783d6
commit faa0429678
5 changed files with 196 additions and 79 deletions
+31 -11
View File
@@ -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:
+78 -48
View File
@@ -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()
+13 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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):
# 清理测试产物