free-short-video 5.3.1 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,6 +23,10 @@ free-short-video
23
23
  [![Docker Hub](https://img.shields.io/docker/pulls/lcy362/free-short-video?label=docker%20pulls)](https://hub.docker.com/r/lcy362/free-short-video)
24
24
  [![npm](https://img.shields.io/npm/v/free-short-video?label=npm)](https://www.npmjs.com/package/free-short-video)
25
25
 
26
+ > **🌏 Mirror Notice / 镜像说明**
27
+ > This project is also mirrored on [Gitee](https://gitee.com/sandgrid/agnes-video-generator) for faster access in mainland China. The **GitHub repository is the primary home** of this project — issues, PRs, and stars are managed there.
28
+ > 本项目在国内 Gitee 设有镜像仓库,便于国内访问加速;**GitHub 为项目主仓库**,Issue / PR / Star 均在 GitHub 提交。
29
+
26
30
  > **Completely free AI video generator** — Built on Agnes AI's free models, no subscription, no high-end GPU, no usage limits. Type in a text idea and automatically generate multi-scene AI videos with narration and subtitles. Supports text-to-video, image-to-video, keyframes animation, digital anchor, and more. All AI compute runs in the cloud — a regular laptop is all you need. **[Try it online →](https://video.lichuanyang.top)**
27
31
 
28
32
  > "The solution is not to suppress AI, but to make it a more equitable capability, so that everyone knows how to create more with AI. This is a very important vision for our company — to make world-class AI belong to everyone. What we can do may be insignificant, but this vision is very long-term and enduring."
package/bin/cli.js CHANGED
@@ -204,9 +204,13 @@ process.on('SIGINT', () => shutdown('SIGINT'));
204
204
  process.on('SIGTERM', () => shutdown('SIGTERM'));
205
205
 
206
206
  // ── auto-open browser ─────────────────────────────────────────────────────
207
+ // 轮询等待服务就绪(HTTP 200)后再打开浏览器,避免启动初期闪现"无法访问"
207
208
  if (openBrowser) {
209
+ const http = require('http');
208
210
  const url = `http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${port}`;
209
- setTimeout(() => {
211
+ let attempts = 0;
212
+
213
+ const openBrowserNow = () => {
210
214
  try {
211
215
  if (process.platform === 'darwin') execSync(`open "${url}"`);
212
216
  else if (process.platform === 'win32') execSync(`start "" "${url}"`);
@@ -214,5 +218,25 @@ if (openBrowser) {
214
218
  } catch (_) {
215
219
  /* browser open is best-effort */
216
220
  }
217
- }, 1500);
221
+ };
222
+
223
+ const scheduleNext = () => {
224
+ if (++attempts > 120) return; // 约 60s 上限,超时不再尝试
225
+ setTimeout(pollReady, 500);
226
+ };
227
+
228
+ const pollReady = () => {
229
+ const req = http.get(url, (res) => {
230
+ res.resume();
231
+ if (res.statusCode >= 200 && res.statusCode < 500) openBrowserNow();
232
+ else scheduleNext();
233
+ });
234
+ req.setTimeout(2000, () => {
235
+ req.destroy();
236
+ scheduleNext();
237
+ });
238
+ req.on('error', scheduleNext);
239
+ };
240
+
241
+ pollReady();
218
242
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-short-video",
3
- "version": "5.3.1",
3
+ "version": "5.5.0",
4
4
  "description": "Free AI short-video generator — run the full free-short-video service locally with one command. Text-to-video, image generation, Edge-TTS narration, subtitles, and compositing, all free. Official site: https://video.lichuanyang.top",
5
5
  "bin": {
6
6
  "free-short-video": "bin/cli.js",
package/server.py CHANGED
@@ -21,7 +21,8 @@ import logging
21
21
  import os
22
22
  from contextlib import asynccontextmanager
23
23
 
24
- from fastapi import FastAPI
24
+ from fastapi import FastAPI, HTTPException
25
+ from fastapi.responses import FileResponse
25
26
  from fastapi.staticfiles import StaticFiles
26
27
 
27
28
  from core.audio.voices import load_voice_catalog
@@ -51,10 +52,24 @@ async def lifespan(app: FastAPI):
51
52
  # 初始化工作目录 / uploads / 错误收集根路径,并重置上次异常退出的遗留任务
52
53
  init_runtime_state()
53
54
 
54
- # v4.0: 预加载音色目录(edge_tts.list_voices),失败不阻断启动
55
+ # v4.0: 预加载音色目录(edge_tts.list_voices)。
56
+ # edge_tts.list_voices() 是网络调用,若网络慢/不可达会阻塞 lifespan 的 yield,
57
+ # 导致服务启动后数秒内不可用。这里限时等待 3 秒:
58
+ # - 正常网络(1~2s):目录在对外服务前就绪,/api/voices 无回退闪烁
59
+ # - 慢网络:超过 3s 立即对外服务,剩余加载转入后台,完成后自动切换完整目录
60
+ async def _load_voice_catalog_bg():
61
+ try:
62
+ await load_voice_catalog()
63
+ logger.info("[Startup] Voice catalog loaded")
64
+ except Exception as e:
65
+ logger.warning(f"[Startup] Voice catalog load failed ({e}); will use fallback")
66
+
55
67
  try:
56
- await load_voice_catalog()
68
+ await asyncio.wait_for(load_voice_catalog(), timeout=3.0)
57
69
  logger.info("[Startup] Voice catalog loaded")
70
+ except asyncio.TimeoutError:
71
+ logger.warning("[Startup] Voice catalog load timed out (>3s); continuing in background")
72
+ asyncio.create_task(_load_voice_catalog_bg())
58
73
  except Exception as e:
59
74
  logger.warning(f"[Startup] Voice catalog load failed ({e}); will use fallback")
60
75
 
@@ -77,7 +92,7 @@ app = FastAPI(
77
92
  description=(
78
93
  "完全免费的 AI 视频生成服务。官网:https://video.lichuanyang.top | "
79
94
  "API 文档:https://video.lichuanyang.top/api-docs | "
80
- "调用指南:https://video.lichuanyang.top/guides/api-call"
95
+ "调用指南:https://video.lichuanyang.top/api-docs"
81
96
  ),
82
97
  lifespan=lifespan,
83
98
  )
@@ -92,6 +107,27 @@ if os.path.exists(static_dir):
92
107
  app.mount("/static", StaticFiles(directory=static_dir), name="static")
93
108
 
94
109
 
110
+ def _serve_static_file(filename: str, media_type: str):
111
+ """返回 static 目录中的文件;不存在时 404(与挂载目录行为一致)。"""
112
+ path = os.path.join(static_dir, filename)
113
+ if os.path.exists(path):
114
+ return FileResponse(path, media_type=media_type)
115
+ raise HTTPException(status_code=404, detail=f"{filename} not found")
116
+
117
+
118
+ # 根路径图标:HTML 虽声明 /static/favicon.ico,但 Safari 等客户端
119
+ # 仍会额外探测根路径 /favicon.ico(及 /icon.png),无路由时每次打开页面
120
+ # 产生一次短暂 404,这里显式补上。
121
+ @app.get("/favicon.ico", include_in_schema=False)
122
+ async def favicon():
123
+ return _serve_static_file("favicon.ico", "image/x-icon")
124
+
125
+
126
+ @app.get("/icon.png", include_in_schema=False)
127
+ async def icon():
128
+ return _serve_static_file("icon.png", "image/png")
129
+
130
+
95
131
  # ═══════════════════════════════════════════════════
96
132
  # 路由汇总(Batch 1 拆分)
97
133
  # ═══════════════════════════════════════════════════