Files
video-create/.claude/skills/video-from-script/scripts/local-draft-server.py
2026-05-20 16:07:24 +08:00

119 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
本地草稿服务器 — 模拟 CapCut Mate API从本地草稿文件夹提供下载。
用法:
python3 local-draft-server.py [--port 8765]
下载器将 API base 指向 http://localhost:8765 即可下载本地草稿。
"""
import os, json, sys, argparse
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
DRAFT_DIR = os.path.expanduser('~/Movies/JianyingPro/User Data/Projects/com.lveditor.draft')
class DraftServer(BaseHTTPRequestHandler):
def log_message(self, format, *args):
print(f' {args[0]}')
def _send_json(self, data, code=200):
body = json.dumps(data, ensure_ascii=False).encode('utf-8')
self.send_response(code)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.send_header('Content-Length', len(body))
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(body)
def _send_file(self, filepath):
if not os.path.exists(filepath):
self._send_json({'code': 404, 'message': 'File not found'}, 404)
return
size = os.path.getsize(filepath)
with open(filepath, 'rb') as f:
self.send_response(200)
self.send_header('Content-Type', 'application/octet-stream')
self.send_header('Content-Length', size)
self.end_headers()
self.wfile.write(f.read())
def do_GET(self):
parsed = urlparse(self.path)
params = parse_qs(parsed.query)
# /openapi/capcut-mate/v1/get_draft 或 /get_draft
if parsed.path.endswith('/get_draft') or '/get_draft' in parsed.path:
draft_id = params.get('draft_id', [None])[0]
if not draft_id:
self._send_json({'code': 1001, 'message': 'Missing draft_id'})
return
draft_path = os.path.join(DRAFT_DIR, draft_id)
if not os.path.isdir(draft_path):
self._send_json({'code': 2001, 'message': f'Draft not found: {draft_id}'})
return
# 收集所有文件 URL
files = []
host = self.headers.get('Host', 'localhost:8765')
base_url = f'http://{host}/files/{draft_id}'
for root, dirs, filenames in os.walk(draft_path):
for fname in filenames:
rel = os.path.relpath(os.path.join(root, fname), draft_path)
files.append(f'{base_url}/{rel}')
print(f'\n[Draft] {draft_id} -> {len(files)} files')
self._send_json({'code': 0, 'message': '成功', 'files': files})
return
# /files/NAME/... -> 提供文件下载
if parsed.path.startswith('/files/'):
parts = parsed.path[7:].split('/', 1) # Remove /files/
if len(parts) >= 2:
draft_id = parts[0]
rel_path = parts[1]
filepath = os.path.join(DRAFT_DIR, draft_id, rel_path)
self._send_file(filepath)
return
# 列出所有可用草稿
if parsed.path == '/' or parsed.path == '/list':
drafts = [d for d in sorted(os.listdir(DRAFT_DIR))
if os.path.isdir(os.path.join(DRAFT_DIR, d))
and not d.endswith('.tmp')
and d.startswith('执黑')]
self._send_json({'code': 0, 'drafts': drafts})
return
self._send_json({'code': 404, 'message': 'Not found'}, 404)
def main():
parser = argparse.ArgumentParser(description='Local CapCut Draft Server')
parser.add_argument('--port', type=int, default=8765, help='Server port (default: 8765)')
args = parser.parse_args()
print(f'\n本地草稿服务器')
print(f'草稿目录: {DRAFT_DIR}')
print(f'服务地址: http://localhost:{args.port}')
print(f'\n可用草稿:')
for d in sorted(os.listdir(DRAFT_DIR)):
if os.path.isdir(os.path.join(DRAFT_DIR, d)) and d.startswith('执黑') and '_v2' not in d:
print(f' http://localhost:{args.port}/get_draft?draft_id={d}')
print(f'\n下载器设置 API Base: http://localhost:{args.port}')
print(f'按 Ctrl+C 停止服务器\n')
server = HTTPServer(('0.0.0.0', args.port), DraftServer)
try:
server.serve_forever()
except KeyboardInterrupt:
print('\n服务器已停止')
server.shutdown()
if __name__ == '__main__':
main()