#!/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()