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 +4 -0
- package/bin/cli.js +26 -2
- package/package.json +1 -1
- package/server.py +40 -4
- package/static/index.html +2922 -66
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ free-short-video
|
|
|
23
23
|
[](https://hub.docker.com/r/lcy362/free-short-video)
|
|
24
24
|
[](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
|
-
|
|
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
|
-
}
|
|
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
|
+
"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/
|
|
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
|
# ═══════════════════════════════════════════════════
|