free-short-video 6.1.0 → 6.2.1
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 +3 -31
- package/core/api/agnes_video.py +105 -3
- package/core/artifacts.py +1 -1
- package/core/config.py +105 -1
- package/core/pipelines/simple_video.py +1 -0
- package/models/task.py +2 -0
- package/package.json +1 -1
- package/static/assets/index-CNMlDGN1.js +98 -0
- package/static/assets/index-CP4jXMrr.css +1 -0
- package/static/index.html +2 -2
- package/web/routes/config_routes.py +13 -2
- package/web/routes/task_creation_routes.py +15 -3
- package/static/assets/index-CCQTBSQr.js +0 -54
- package/static/assets/index-Didd6fOX.css +0 -1
package/README.md
CHANGED
|
@@ -1,40 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
|
|
3
|
-
# What's New in v6.1
|
|
3
|
+
# What's New in v6.2.1
|
|
4
4
|
|
|
5
5
|
## What's New
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### Bug Fixes
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- **Retry Task button** — resumes the task from the failed step via the existing checkpoint resume (no new backend endpoint, no full resubmit).
|
|
12
|
-
- **Retry count tracking** — retries are counted per task and persist across refreshes; the count resets when the task succeeds.
|
|
13
|
-
- **Transient-failure copy** — the panel explains that most failures come from temporary rate limiting / network timeouts / model-service fluctuation and are worth retrying before reporting.
|
|
14
|
-
|
|
15
|
-
### One-Click Issue Feedback with Progressive Expansion
|
|
16
|
-
|
|
17
|
-
When retries don't help, the feedback area helps you report efficiently:
|
|
18
|
-
|
|
19
|
-
- **Progressive expansion** — the feedback area stays a low-key hint line under the retry guidance; after **2 failed retries** it auto-expands, switching the copy to "still failing after N retries, please report with diagnostic info." You can open it manually at any time (your choice is remembered per task).
|
|
20
|
-
- **Deterministic-failure pre-screen** — parameter/content errors (HTTP 400, content policy, invalid API key) are detected from the error text, skip the retry guidance, and expand the feedback area directly with a "retrying may not help" hint and a de-emphasized retry button.
|
|
21
|
-
- **Diagnostic info preview** — a collapsible structured report: app version, task type, mode, failed step, error message, retry count, and key generation configs (no prompt text included).
|
|
22
|
-
- **Copy diagnostic info** — one click copies the full report for pasting anywhere.
|
|
23
|
-
- **Check the FAQ first** — a direct link to the official FAQ page for self-service troubleshooting.
|
|
24
|
-
- **Open a GitHub Issue** — jumps to the project issue tracker with title and body pre-filled from the diagnostic report (long bodies are safely truncated); a bug-report issue template is available in the repo.
|
|
25
|
-
|
|
26
|
-
### Diagnostics Endpoint (Phase 2)
|
|
27
|
-
|
|
28
|
-
- **`GET /api/tasks/{id}/diagnostics`** — returns a task summary plus the model-call error details associated with that task (exact task match first, time-window fallback), sourced from the existing `error_logs/` collector.
|
|
29
|
-
- **Task-linked error collection** — model errors are now written with their `task_id`, so a failure can be traced back to the exact API calls that caused it.
|
|
30
|
-
- **Privacy by design** — the endpoint never returns prompt text, response bodies, or system prompts; error messages are truncated. The feedback report is fully client-side and **nothing is uploaded automatically**.
|
|
31
|
-
|
|
32
|
-
### Other Features & Improvements
|
|
33
|
-
|
|
34
|
-
- **App version in diagnostics** — `APP_VERSION` is now exposed via `GET /api/config`, so every report carries the exact version that failed (kept in sync by the release process).
|
|
35
|
-
- **Official FAQ updated** — new entries explain what to do when generation fails (retry first, then report with diagnostic info) and how to get help / report issues; synced across the official site and wiki.
|
|
36
|
-
|
|
37
|
-
---
|
|
9
|
+
- **Localized feedback & diagnostic report** — the diagnostic report header, field names and the generated issue (GitHub) template are no longer hard-coded in Chinese. They now respect your UI language (22 languages), so fields, titles and truncation hints in the feedback panel render correctly for non-Chinese users.
|
|
38
10
|
|
|
39
11
|
---
|
|
40
12
|
|
package/core/api/agnes_video.py
CHANGED
|
@@ -14,7 +14,12 @@ import requests
|
|
|
14
14
|
from core.api.error_collector import collect_error, collect_error_from_exception
|
|
15
15
|
from core.api.key_manager import get_key_ring
|
|
16
16
|
from core.api.rate_limiter import get_rate_limiter, get_video_submit_limiter
|
|
17
|
-
from core.config import
|
|
17
|
+
from core.config import (
|
|
18
|
+
VIDEO_ASPECT_RATIOS,
|
|
19
|
+
get_agnes_api_root,
|
|
20
|
+
get_agnes_base_url,
|
|
21
|
+
is_v25_video_model,
|
|
22
|
+
)
|
|
18
23
|
from utils.image_normalizer import normalize_reference_path
|
|
19
24
|
from utils.video import download_video
|
|
20
25
|
|
|
@@ -236,9 +241,11 @@ class AgnesVideoAPI:
|
|
|
236
241
|
poll_count = 0
|
|
237
242
|
consecutive_failures = 0
|
|
238
243
|
start_time = asyncio.get_event_loop().time()
|
|
244
|
+
# 2.5 系列查询需带 model_name(text 模式可省略,但带上更通用)
|
|
245
|
+
model_param = f"&model_name={self.model}" if is_v25_video_model(self.model) else ""
|
|
239
246
|
curl_cmd = (
|
|
240
247
|
f'curl -s -H "Authorization: Bearer $AGNES_API_KEY" '
|
|
241
|
-
f'"{get_agnes_api_root()}/agnesapi?video_id={video_id}"'
|
|
248
|
+
f'"{get_agnes_api_root()}/agnesapi?video_id={video_id}{model_param}"'
|
|
242
249
|
)
|
|
243
250
|
while True:
|
|
244
251
|
# M2: 每次轮询前检查停止信号
|
|
@@ -271,7 +278,7 @@ class AgnesVideoAPI:
|
|
|
271
278
|
resp = await asyncio.wait_for(
|
|
272
279
|
asyncio.to_thread(
|
|
273
280
|
requests.get,
|
|
274
|
-
f"{get_agnes_api_root()}/agnesapi?video_id={video_id}",
|
|
281
|
+
f"{get_agnes_api_root()}/agnesapi?video_id={video_id}{model_param}",
|
|
275
282
|
headers=self._auth_headers(),
|
|
276
283
|
timeout=15,
|
|
277
284
|
),
|
|
@@ -525,6 +532,17 @@ class AgnesVideoAPI:
|
|
|
525
532
|
negative_prompt: Optional[str] = None,
|
|
526
533
|
**kwargs,
|
|
527
534
|
) -> str:
|
|
535
|
+
# 2.5 系列模型(v6.2):新参数协议(mode/seconds/size/aspect_ratio)
|
|
536
|
+
if is_v25_video_model(self.model):
|
|
537
|
+
return await self._submit_video_v25(
|
|
538
|
+
prompt=prompt,
|
|
539
|
+
reference_image_paths=reference_image_paths,
|
|
540
|
+
duration=duration,
|
|
541
|
+
width=width,
|
|
542
|
+
height=height,
|
|
543
|
+
seed=seed,
|
|
544
|
+
**kwargs,
|
|
545
|
+
)
|
|
528
546
|
num_frames, frame_rate = self._get_frame_config(duration, width, height)
|
|
529
547
|
|
|
530
548
|
payload: dict = {
|
|
@@ -568,6 +586,90 @@ class AgnesVideoAPI:
|
|
|
568
586
|
logger.info(f"[AgnesVideo] Video submitted: {video_id[:20]}...")
|
|
569
587
|
return video_id
|
|
570
588
|
|
|
589
|
+
@staticmethod
|
|
590
|
+
def _width_height_to_aspect_ratio(width: int, height: int) -> str:
|
|
591
|
+
"""将像素宽高映射到最接近的 2.5 系列 aspect_ratio 枚举。
|
|
592
|
+
|
|
593
|
+
找不到精确比例时取误差最小的档位(默认 16:9)。
|
|
594
|
+
"""
|
|
595
|
+
if not width or not height:
|
|
596
|
+
return "16:9"
|
|
597
|
+
ratio = width / height
|
|
598
|
+
best = "16:9"
|
|
599
|
+
best_dist = float("inf")
|
|
600
|
+
for ar in VIDEO_ASPECT_RATIOS:
|
|
601
|
+
w, h = ar.split(":")
|
|
602
|
+
target = int(w) / int(h)
|
|
603
|
+
dist = abs(ratio - target)
|
|
604
|
+
if dist < best_dist:
|
|
605
|
+
best_dist = dist
|
|
606
|
+
best = ar
|
|
607
|
+
return best
|
|
608
|
+
|
|
609
|
+
async def _submit_video_v25(
|
|
610
|
+
self,
|
|
611
|
+
prompt: str,
|
|
612
|
+
reference_image_paths: List[str],
|
|
613
|
+
duration: Optional[int] = None,
|
|
614
|
+
width: int = 1152,
|
|
615
|
+
height: int = 768,
|
|
616
|
+
seed: Optional[int] = None,
|
|
617
|
+
**kwargs,
|
|
618
|
+
) -> str:
|
|
619
|
+
"""2.5 / 2.5-flash 新协议提交:mode / seconds / size / aspect_ratio。
|
|
620
|
+
|
|
621
|
+
与 v2.0 的关键差异:
|
|
622
|
+
- size 固定 720P(flash)或 720P/960P/2K(2.5);不传 width/height/num_frames
|
|
623
|
+
- seconds 为字符串 "4"–"12"
|
|
624
|
+
- 单参考图 → reference(images);多图 → keyframe(first_frame + last_frame)
|
|
625
|
+
- 不支持 negative_prompt(忽略)
|
|
626
|
+
"""
|
|
627
|
+
# 时长:duration(秒) → seconds 字符串(4–12,超界截断)
|
|
628
|
+
secs = int(duration) if duration else 5
|
|
629
|
+
seconds = str(max(4, min(secs, 12)))
|
|
630
|
+
# 分辨率:flash 固定 720P;2.5 用 video_size 参数(缺省 720P)
|
|
631
|
+
size = kwargs.get("video_size") or "720P"
|
|
632
|
+
if self.model == "agnes-video-2.5-flash":
|
|
633
|
+
size = "720P"
|
|
634
|
+
aspect_ratio = self._width_height_to_aspect_ratio(width, height)
|
|
635
|
+
|
|
636
|
+
payload: dict = {
|
|
637
|
+
"model": self.model,
|
|
638
|
+
"prompt": prompt,
|
|
639
|
+
"mode": "text",
|
|
640
|
+
"seconds": seconds,
|
|
641
|
+
"size": size,
|
|
642
|
+
"aspect_ratio": aspect_ratio,
|
|
643
|
+
}
|
|
644
|
+
if seed is not None:
|
|
645
|
+
payload["seed"] = seed
|
|
646
|
+
|
|
647
|
+
# 参考图解析(与 v2.0 一致:归一化 + 上传为公开 URL)
|
|
648
|
+
resolved_refs = []
|
|
649
|
+
for p in reference_image_paths:
|
|
650
|
+
norm = await asyncio.to_thread(normalize_reference_path, p, width, height)
|
|
651
|
+
resolved_refs.append(await self._resolve_image_ref(norm))
|
|
652
|
+
n_refs = len(resolved_refs)
|
|
653
|
+
|
|
654
|
+
if n_refs == 1:
|
|
655
|
+
payload["mode"] = "reference"
|
|
656
|
+
payload["images"] = resolved_refs
|
|
657
|
+
mode_desc = "reference (1 image)"
|
|
658
|
+
elif n_refs >= 2:
|
|
659
|
+
# v6.2.1: Agnes 2.5 系列 keyframe 模式固定输出 704x704 正方形
|
|
660
|
+
# (忽略 aspect_ratio,竖屏/横屏画面会被拉伸,人物显宽)。
|
|
661
|
+
# 降级为 reference 模式(多图参考,≤5 张),输出遵循 aspect_ratio。
|
|
662
|
+
payload["mode"] = "reference"
|
|
663
|
+
payload["images"] = resolved_refs[:5]
|
|
664
|
+
mode_desc = f"reference ({n_refs} images, keyframe fallback)"
|
|
665
|
+
else:
|
|
666
|
+
mode_desc = "text-to-video"
|
|
667
|
+
|
|
668
|
+
logger.info(f"[AgnesVideo] {mode_desc}: {prompt[:80]}...")
|
|
669
|
+
video_id = await self._submit_with_retry(payload, mode_desc)
|
|
670
|
+
logger.info(f"[AgnesVideo] Video submitted: {video_id[:20]}...")
|
|
671
|
+
return video_id
|
|
672
|
+
|
|
571
673
|
async def wait_for_video(self, video_id: str, progress_callback=None) -> VideoOutput:
|
|
572
674
|
final = await self._poll_task(video_id, progress_callback=progress_callback)
|
|
573
675
|
|
package/core/artifacts.py
CHANGED
|
@@ -1082,7 +1082,7 @@ def write_manifest_md(state: BaseTaskState, task_dir: str) -> str:
|
|
|
1082
1082
|
lines += [
|
|
1083
1083
|
"## 协作提示",
|
|
1084
1084
|
"",
|
|
1085
|
-
"- 文本 / JSON / SRT 产物可直接用编辑器或外部 AI Agent(如 opencode、workbuddy)修改。",
|
|
1085
|
+
"- 文本 / JSON / SRT 产物可直接用编辑器或外部 AI Agent(如 TraeWork、opencode、workbuddy)修改。",
|
|
1086
1086
|
"- 图片 / 视频产物可用 ffmpeg、Python 等外部工具处理后覆盖同名文件。",
|
|
1087
1087
|
"- 产物字段说明见 `docs/dev/artifact_standard.md`。",
|
|
1088
1088
|
"",
|
package/core/config.py
CHANGED
|
@@ -18,7 +18,7 @@ CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
|
|
|
18
18
|
# ═══════════════════════════════════════════════════
|
|
19
19
|
# 应用版本号(v6.1 新增:发版时同步更新,见 docs/dev/release_process.md)
|
|
20
20
|
# ═══════════════════════════════════════════════════
|
|
21
|
-
APP_VERSION = "6.1
|
|
21
|
+
APP_VERSION = "6.2.1"
|
|
22
22
|
|
|
23
23
|
# 未配置 API Key 时的统一报错文案(含免费获取与在线体验兜底,全站路由共用)
|
|
24
24
|
API_KEY_MISSING_MSG = (
|
|
@@ -739,6 +739,110 @@ def set_selected_models(text: str = None, image: str = None, video: str = None)
|
|
|
739
739
|
return get_selected_models()
|
|
740
740
|
|
|
741
741
|
|
|
742
|
+
# ═══════════════════════════════════════════════════
|
|
743
|
+
# 视频模型能力元数据(v6.2:选模型阶段差异说明 + 表单选项归拢)
|
|
744
|
+
# ═══════════════════════════════════════════════════
|
|
745
|
+
|
|
746
|
+
# 2.5 系列模型 ID(新参数协议:mode/seconds/size/aspect_ratio,与 v2.0 不兼容)
|
|
747
|
+
VIDEO_MODEL_25_PREFIX = "agnes-video-2.5"
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def is_v25_video_model(model: str) -> bool:
|
|
751
|
+
"""判断是否为 2.5 系列视频模型(新参数协议)。"""
|
|
752
|
+
return bool(model) and model.startswith(VIDEO_MODEL_25_PREFIX)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
# 支持的画幅比例(2.5 系列 aspect_ratio 枚举)
|
|
756
|
+
VIDEO_ASPECT_RATIOS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"]
|
|
757
|
+
|
|
758
|
+
# 2.5 系列时长档位(seconds 字符串 "4"–"12")
|
|
759
|
+
VIDEO_25_DURATIONS = [4, 5, 6, 8, 10, 12]
|
|
760
|
+
|
|
761
|
+
# 视频模型能力元数据:供前端「选模型阶段」展示差异 + 按模型归拢表单选项
|
|
762
|
+
VIDEO_MODEL_CAPABILITIES = {
|
|
763
|
+
DEFAULT_VIDEO_MODEL: { # agnes-video-v2.0(免费旧版)
|
|
764
|
+
"label": "Video 2.0",
|
|
765
|
+
"price": "free",
|
|
766
|
+
"price_text": {"zh": "免费", "en": "Free"},
|
|
767
|
+
"modes": [
|
|
768
|
+
{"id": "t2v", "label": {"zh": "文生视频", "en": "Text to video"}},
|
|
769
|
+
{"id": "i2v", "label": {"zh": "图生视频(1 张参考图)", "en": "Image to video (1 ref)"}},
|
|
770
|
+
{"id": "keyframes", "label": {"zh": "关键帧动画(首尾帧)", "en": "Keyframes (first+last)"}},
|
|
771
|
+
],
|
|
772
|
+
"durations": [5, 10, 15, 18, 20],
|
|
773
|
+
"resolution": {
|
|
774
|
+
"type": "pixels",
|
|
775
|
+
"options": [
|
|
776
|
+
{"value": "768x1152", "label": {"zh": "竖屏 768x1152", "en": "Portrait 768x1152"}},
|
|
777
|
+
{"value": "1152x768", "label": {"zh": "横屏 1152x768", "en": "Landscape 1152x768"}},
|
|
778
|
+
{"value": "1024x1024", "label": {"zh": "方形 1024x1024", "en": "Square 1024x1024"}},
|
|
779
|
+
],
|
|
780
|
+
},
|
|
781
|
+
"supports_negative": True,
|
|
782
|
+
"max_ref_images": None, # 不限
|
|
783
|
+
"supports_ref_video": False,
|
|
784
|
+
"desc": {
|
|
785
|
+
"zh": "免费旧版。支持任意像素分辨率、最长约 17 秒、多参考图关键帧;有负面提示词。",
|
|
786
|
+
"en": "Free legacy model. Arbitrary pixel resolution, up to ~17s, multi-image keyframes; supports negative prompt.",
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
"agnes-video-2.5": { # 付费模型
|
|
790
|
+
"label": "Video 2.5",
|
|
791
|
+
"price": "paid",
|
|
792
|
+
"price_text": {
|
|
793
|
+
"zh": "付费:720P $0.025/s · 960P $0.04/s · 2K $0.055/s;图片前 5 张免费",
|
|
794
|
+
"en": "Paid: 720P $0.025/s · 960P $0.04/s · 2K $0.055/s; first 5 images free",
|
|
795
|
+
},
|
|
796
|
+
"modes": [
|
|
797
|
+
{"id": "text", "label": {"zh": "文生视频", "en": "Text to video"}},
|
|
798
|
+
{"id": "keyframe", "label": {"zh": "首尾帧(自动降级图片参考)", "en": "First/last frame (auto fallback)"}},
|
|
799
|
+
{"id": "reference", "label": {"zh": "图片/音频参考", "en": "Image/audio reference"}},
|
|
800
|
+
],
|
|
801
|
+
"durations": VIDEO_25_DURATIONS,
|
|
802
|
+
"resolution": {
|
|
803
|
+
"type": "ratio_size",
|
|
804
|
+
"ratios": VIDEO_ASPECT_RATIOS,
|
|
805
|
+
"sizes": ["720P", "960P", "2K"],
|
|
806
|
+
},
|
|
807
|
+
"supports_negative": False,
|
|
808
|
+
"max_ref_images": 5, # 第 6 张起 $0.005/张
|
|
809
|
+
"supports_ref_video": True,
|
|
810
|
+
"desc": {
|
|
811
|
+
"zh": "新一代付费模型。最高 2K 分辨率、4–12 秒、支持参考视频;不支持负面提示词。",
|
|
812
|
+
"en": "New paid model. Up to 2K, 4–12s, supports reference videos; no negative prompt.",
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
"agnes-video-2.5-flash": { # 免费新版(限时免费)
|
|
816
|
+
"label": "Video 2.5 Flash",
|
|
817
|
+
"price": "free",
|
|
818
|
+
"price_text": {"zh": "免费(限时)", "en": "Free (limited time)"},
|
|
819
|
+
"modes": [
|
|
820
|
+
{"id": "text", "label": {"zh": "文生视频", "en": "Text to video"}},
|
|
821
|
+
{"id": "keyframe", "label": {"zh": "首尾帧(自动降级图片参考)", "en": "First/last frame (auto fallback)"}},
|
|
822
|
+
{"id": "reference", "label": {"zh": "图片参考(≤5 张)", "en": "Image reference (≤5)"}},
|
|
823
|
+
],
|
|
824
|
+
"durations": VIDEO_25_DURATIONS,
|
|
825
|
+
"resolution": {
|
|
826
|
+
"type": "ratio_size",
|
|
827
|
+
"ratios": VIDEO_ASPECT_RATIOS,
|
|
828
|
+
"sizes": ["720P"], # 固定 720P
|
|
829
|
+
},
|
|
830
|
+
"supports_negative": False,
|
|
831
|
+
"max_ref_images": 5,
|
|
832
|
+
"supports_ref_video": False,
|
|
833
|
+
"desc": {
|
|
834
|
+
"zh": "免费新版(限时免费)。固定 720P、4–12 秒、图片参考最多 5 张;不支持负面提示词与参考视频。",
|
|
835
|
+
"en": "Free new model (limited time). Fixed 720P, 4–12s, up to 5 ref images; no negative prompt or ref video.",
|
|
836
|
+
},
|
|
837
|
+
},
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def get_video_model_capabilities() -> dict:
|
|
842
|
+
"""返回全部视频模型能力元数据(含未知模型的兜底默认)。"""
|
|
843
|
+
return dict(VIDEO_MODEL_CAPABILITIES)
|
|
844
|
+
|
|
845
|
+
|
|
742
846
|
# ═══════════════════════════════════════════════════
|
|
743
847
|
# Agnes API 域名配置(v6.0)
|
|
744
848
|
# ═══════════════════════════════════════════════════
|
|
@@ -135,6 +135,7 @@ class SimpleVideoPipeline(BasePipeline):
|
|
|
135
135
|
height=self._state.video_height,
|
|
136
136
|
seed=self._state.seed,
|
|
137
137
|
negative_prompt=self._state.negative_prompt,
|
|
138
|
+
video_size=getattr(self._state, "video_size", None) or "720P",
|
|
138
139
|
)
|
|
139
140
|
|
|
140
141
|
# 持久化 video_id + curl 命令
|
package/models/task.py
CHANGED
|
@@ -213,6 +213,8 @@ class SimpleVideoTask(BaseTaskState):
|
|
|
213
213
|
reference_image: str = ""
|
|
214
214
|
end_frame_image: str = ""
|
|
215
215
|
duration: int = 5
|
|
216
|
+
# v6.2:2.5 系列模型分辨率档位(720P/960P/2K;v2.0 忽略,默认 720P)
|
|
217
|
+
video_size: str = "720P"
|
|
216
218
|
seed: Optional[int] = None
|
|
217
219
|
negative_prompt: Optional[str] = None
|
|
218
220
|
system_prompt: str = ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-short-video",
|
|
3
|
-
"version": "6.1
|
|
3
|
+
"version": "6.2.1",
|
|
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",
|