test-video-generator.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>视频生成测试</title>
  7. <style>
  8. * { margin: 0; padding: 0; box-sizing: border-box; }
  9. body {
  10. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  11. padding: 20px;
  12. background: #f5f5f5;
  13. }
  14. .container { max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); }
  15. h1 { text-align: center; margin-bottom: 30px; color: #333; }
  16. .form-group { margin-bottom: 20px; }
  17. label { display: block; margin-bottom: 8px; font-weight: 600; color: #333; }
  18. input[type="text"], textarea, input[type="file"] {
  19. width: 100%;
  20. padding: 12px;
  21. border: 1px solid #ddd;
  22. border-radius: 8px;
  23. font-size: 14px;
  24. }
  25. textarea { min-height: 120px; resize: vertical; }
  26. button {
  27. width: 100%;
  28. padding: 14px;
  29. border: none;
  30. border-radius: 8px;
  31. font-size: 16px;
  32. font-weight: 600;
  33. cursor: pointer;
  34. margin-bottom: 10px;
  35. }
  36. .btn-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
  37. .btn-secondary { background: #f5f5f5; color: #333; }
  38. button:hover { opacity: 0.9; }
  39. button:disabled { opacity: 0.5; cursor: not-allowed; }
  40. .result {
  41. margin-top: 20px;
  42. padding: 15px;
  43. background: #f9f9f9;
  44. border-radius: 8px;
  45. border-left: 4px solid #667eea;
  46. }
  47. .result h3 { margin-bottom: 10px; color: #333; }
  48. .result pre {
  49. background: #f5f5f5;
  50. padding: 10px;
  51. border-radius: 4px;
  52. overflow-x: auto;
  53. font-size: 12px;
  54. max-height: 300px;
  55. overflow-y: auto;
  56. }
  57. .status { padding: 10px; border-radius: 8px; margin-top: 15px; text-align: center; }
  58. .status.loading { background: #fff3cd; color: #856404; }
  59. .status.success { background: #d4edda; color: #155724; }
  60. .status.error { background: #f8d7da; color: #721c24; }
  61. </style>
  62. </head>
  63. <body>
  64. <div class="container">
  65. <h1>🎬 视频生成测试</h1>
  66. <div class="form-group">
  67. <label>视频标题:</label>
  68. <input type="text" id="title" value="测试视频" placeholder="输入视频标题">
  69. </div>
  70. <div class="form-group">
  71. <label>上传图片 (支持多选):</label>
  72. <input type="file" id="imageInput" accept="image/*" multiple>
  73. <div id="imagePreview" style="margin-top: 10px;"></div>
  74. </div>
  75. <div class="form-group">
  76. <label>上传音频:</label>
  77. <input type="file" id="audioInput" accept="audio/*">
  78. </div>
  79. <button class="btn-primary" onclick="testAPI()">1. 测试API连接</button>
  80. <button class="btn-primary" onclick="uploadAndCreate()">2. 上传文件并创建项目</button>
  81. <button class="btn-secondary" onclick="getProjectList()">3. 获取项目列表</button>
  82. <div id="result" class="result" style="display: none;">
  83. <h3>结果:</h3>
  84. <pre id="resultContent"></pre>
  85. </div>
  86. <div id="status" class="status" style="display: none;"></div>
  87. </div>
  88. <script>
  89. const API_BASE = 'http://localhost:3000/api';
  90. function showStatus(message, type) {
  91. const status = document.getElementById('status');
  92. status.textContent = message;
  93. status.className = 'status ' + type;
  94. status.style.display = 'block';
  95. }
  96. function showResult(data) {
  97. const result = document.getElementById('result');
  98. const content = document.getElementById('resultContent');
  99. result.style.display = 'block';
  100. content.textContent = JSON.stringify(data, null, 2);
  101. }
  102. async function testAPI() {
  103. showStatus('正在测试API连接...', 'loading');
  104. try {
  105. const res = await fetch(API_BASE + '/video/projects');
  106. const data = await res.json();
  107. showStatus('API连接成功!', 'success');
  108. showResult({ message: 'API连接测试成功', data });
  109. } catch (error) {
  110. showStatus('API连接失败: ' + error.message, 'error');
  111. showResult({ error: error.message, hint: '请确保后端服务已启动 (npm run dev)' });
  112. }
  113. }
  114. async function uploadAndCreate() {
  115. const title = document.getElementById('title').value;
  116. const imageInput = document.getElementById('imageInput');
  117. const audioInput = document.getElementById('audioInput');
  118. if (!title) {
  119. showStatus('请输入视频标题', 'error');
  120. return;
  121. }
  122. if (imageInput.files.length === 0) {
  123. showStatus('请选择至少一张图片', 'error');
  124. return;
  125. }
  126. showStatus('正在上传文件...', 'loading');
  127. try {
  128. // 1. 上传图片
  129. const imageUrls = [];
  130. for (let i = 0; i < imageInput.files.length; i++) {
  131. const file = imageInput.files[i];
  132. const formData = new FormData();
  133. formData.append('file', file);
  134. formData.append('type', 'image');
  135. formData.append('name', file.name);
  136. formData.append('category', 'custom');
  137. const res = await fetch(API_BASE + '/video/materials/upload', {
  138. method: 'POST',
  139. body: formData
  140. });
  141. if (!res.ok) throw new Error('图片上传失败');
  142. const data = await res.json();
  143. imageUrls.push(data.data?.url || `/uploads/materials/${file.name}`);
  144. showStatus(`图片 ${i + 1}/${imageInput.files.length} 上传成功`, 'loading');
  145. }
  146. // 2. 上传音频(如果有)
  147. let audioUrl = '';
  148. if (audioInput.files.length > 0) {
  149. const file = audioInput.files[0];
  150. const formData = new FormData();
  151. formData.append('file', file);
  152. formData.append('type', 'audio');
  153. formData.append('name', file.name);
  154. formData.append('category', 'custom');
  155. const res = await fetch(API_BASE + '/video/materials/upload', {
  156. method: 'POST',
  157. body: formData
  158. });
  159. if (!res.ok) throw new Error('音频上传失败');
  160. const data = await res.json();
  161. audioUrl = data.data?.url || `/uploads/materials/${file.name}`;
  162. showStatus('音频上传成功', 'loading');
  163. }
  164. // 3. 创建项目
  165. const projectData = {
  166. title: title,
  167. config: {
  168. video: { width: 720, height: 1280, fps: 30 },
  169. kenburns: { enabled: true, minZoom: 1.0, maxZoom: 1.2, zoomCurve: 'ease-in-out' },
  170. audio: { url: audioUrl, volume: 1.0 },
  171. images: imageUrls.map(url => ({ url, duration: 5, transition: 'fade' })),
  172. subtitle: { text: title, position: 'bottom', fontSize: 24, fontColor: 'white' }
  173. }
  174. };
  175. showStatus('正在创建项目...', 'loading');
  176. const createRes = await fetch(API_BASE + '/video/projects', {
  177. method: 'POST',
  178. headers: { 'Content-Type': 'application/json' },
  179. body: JSON.stringify(projectData)
  180. });
  181. const createData = await createRes.json();
  182. if (createData.success) {
  183. showStatus('项目创建成功!', 'success');
  184. showResult({
  185. message: '视频项目创建成功!',
  186. project: createData.data,
  187. images: imageUrls,
  188. audio: audioUrl
  189. });
  190. } else {
  191. throw new Error(createData.error || '创建失败');
  192. }
  193. } catch (error) {
  194. showStatus('操作失败: ' + error.message, 'error');
  195. showResult({ error: error.message });
  196. }
  197. }
  198. async function getProjectList() {
  199. showStatus('正在获取项目列表...', 'loading');
  200. try {
  201. const res = await fetch(API_BASE + '/video/projects?pageSize=10');
  202. const data = await res.json();
  203. showStatus('获取成功!', 'success');
  204. showResult({ message: '项目列表', projects: data.data });
  205. } catch (error) {
  206. showStatus('获取失败: ' + error.message, 'error');
  207. showResult({ error: error.message });
  208. }
  209. }
  210. // 图片预览
  211. document.getElementById('imageInput').addEventListener('change', function(e) {
  212. const preview = document.getElementById('imagePreview');
  213. preview.innerHTML = '';
  214. Array.from(this.files).forEach(file => {
  215. const reader = new FileReader();
  216. reader.onload = function(e) {
  217. const img = document.createElement('img');
  218. img.src = e.target.result;
  219. img.style.cssText = 'width: 80px; height: 80px; object-fit: cover; border-radius: 8px; margin-right: 10px;';
  220. preview.appendChild(img);
  221. };
  222. reader.readAsDataURL(file);
  223. });
  224. });
  225. </script>
  226. </body>
  227. </html>