free-short-video 6.4.4 → 6.4.6

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/.env.example CHANGED
@@ -16,22 +16,32 @@
16
16
 
17
17
  # ── 1. API Key(必填)──────────────────────────────────────────────
18
18
  # 从 https://platform.agnes-ai.com 获取免费 Key
19
+ # ⚠️ 主 Key 的变量名不带序号(就是 AGNES_API_KEY,没有 AGNES_API_KEY_1 这个槽位)
19
20
  AGNES_API_KEY=your-api-key-here
20
21
 
21
22
 
22
23
  # ── 2. 多 Key 轮询(可选,推荐)────────────────────────────────────
23
24
  # 每个 Key 的配额独立,总量 ≈ 20 × Key 数 / 分钟。
24
- # 命名规则:AGNES_API_KEY_2、AGNES_API_KEY_3 ... 序号依次递增,中间不可断号
25
- # (断号之后的 Key 不会被加载)。
25
+ # 命名规则:主 Key 用 AGNES_API_KEY,额外 Key 从 AGNES_API_KEY_2 起序号依次递增
26
+ # (_2、_3、_4 ...),中间不可断号(断号之后的 Key 不会被加载)。
26
27
  # 遇到 429 会自动轮换到下一个 Key;限速配额与并发上限随 Key 数线性放大。
27
28
  # AGNES_API_KEY_2=your-second-api-key
28
29
  # AGNES_API_KEY_3=your-third-api-key
30
+ # AGNES_API_KEY_4=your-fourth-api-key
31
+ # AGNES_API_KEY_5=your-fifth-api-key
29
32
 
30
33
 
31
- # ── 3. 服务地址 ────────────────────────────────────────────────────
34
+ # ── 3. 本地服务监听地址(非 Agnes API 地址)─────────────────────────
32
35
  HOST=0.0.0.0
33
36
  PORT=8765
34
37
 
38
+ # 注:Agnes API 走哪个站点(国际站 / 国内站)**不通过环境变量配置**
39
+ # (AGNES_BASE_URL 已废弃,代码中不再读取)。请在 Web 设置页的 API Key 面板
40
+ # 选择域名,或用「自动探测域名」逐 Key 补全匹配域名(每个 Key 可绑定各自域名)。
41
+ # 可选域名:com = apihub.agnes-ai.com(国际站)、cn = api.agnes-ai.cn(国内站,
42
+ # 需国内站专属 Key)、cn_bak = apihub.agnes-ai.cn(备用,国际站 Key 亦可用)。
43
+ # 跨站使用 Key 会得到 HTTP 401 / 无效的令牌。
44
+
35
45
 
36
46
  # ── 4. 限速(可选)─────────────────────────────────────────────────
37
47
  # 共享桶:Chat / 图片生成 / 图片上传 / 结果轮询 共用
@@ -54,8 +64,10 @@ PORT=8765
54
64
 
55
65
 
56
66
  # ── 5. 图生图模型(可选)───────────────────────────────────────────
57
- # i2i 默认与 t2i 同模型(agnes-image-2.1-flash);如需回退到 2.0:
58
- # AGNES_IMAGE_I2I_MODEL=agnes-image-2.0
67
+ # i2i 默认与 t2i 同模型(当前默认 agnes-image-2.5-flash);如需为 i2i 固定旧版:
68
+ # AGNES_IMAGE_I2I_MODEL=agnes-image-2.1-flash
69
+ # (真实模型 ID 形如 agnes-image-2.5-flash / agnes-image-2.1-flash /
70
+ # agnes-image-2.0-flash,不带 -flash 后缀的写法无效)
59
71
  #
60
72
  # 注:视频 / 图片主模型请通过 Web UI 或 POST /api/config 选择,
61
73
  # 不再通过环境变量配置(AGNES_VIDEO_MODEL、AGNES_IMAGE_MODEL、
package/README.md CHANGED
@@ -1,20 +1,16 @@
1
1
  ---
2
2
 
3
- # What's New in v6.4.4
3
+ # What's New in v6.4.6
4
4
 
5
5
  ## What's New
6
6
 
7
- ### Features & Improvements
7
+ ### Bug Fixes
8
8
 
9
- * **Image model selection enabled** — you can now choose the image model used for reference images, end frames, and standalone image generation directly in the Settings panel, instead of it being fixed to the built-in default.
10
-
11
- * **Agnes Image 2.5 Flash added and set as default** — the latest generation image model `agnes-image-2.5-flash` is now the default for both text-to-image and image-to-image workflows; the older `agnes-image-2.1-flash` and `agnes-image-2.0-flash` remain available as selectable options.
12
-
13
- * **LLM upgraded to Agnes 2.5 Flash** — the deprecated `agnes-2.0-flash` text model is removed from the selector and replaced by `agnes-2.5-flash` for story, script, and narration generation, following the official model deprecation notice.
9
+ * **Digital human (anchor) videos no longer fail at the stitch/composite step in Docker** — anchor tasks crashed while looping the presenter clip and compositing it with narration audio and subtitles, and retrying did not help because the underlying cause was permanent. The container ships only the bundled `ffmpeg` binary (no `ffprobe`), and the clip-duration probe did not handle a missing `ffprobe`. Media duration is now probed through `ffmpeg` whenever `ffprobe` is unavailable, with a safe default as a final fallback, so digital-human video composition completes normally in Docker and other minimal environments.
14
10
 
15
11
  ---
16
12
 
17
- This is a maintenance release focused on model flexibility: open image model selection, a newer default image model, and migration off the deprecated text model.
13
+ No action is required after upgrading.
18
14
 
19
15
  ---
20
16
 
@@ -37,7 +33,7 @@ free-short-video
37
33
 
38
34
  [![中文](https://img.shields.io/badge/CN-中文-red)](/README_ZH.md)
39
35
  [![GitHub Stars](https://img.shields.io/github/stars/lcy362/agnes-video-generator?style=social)](https://github.com/lcy362/agnes-video-generator)
40
- [![License](https://img.shields.io/github/license/lcy362/agnes-video-generator)](https://github.com/lcy362/agnes-video-generator/blob/main/LICENSE)
36
+ [![License](https://img.shields.io/github/license/lcy362/agnes-video-generator)](https://github.com/lcy362/agnes-video-generator/blob/HEAD/LICENSE)
41
37
  [![Python](https://img.shields.io/badge/python-3.10+-blue)](https://www.python.org/)
42
38
  [![Website](https://img.shields.io/badge/website-video.lichuanyang.top-8A2BE2)](https://video.lichuanyang.top)
43
39
  [![Docker Hub](https://img.shields.io/docker/pulls/lcy362/free-short-video?label=docker%20pulls)](https://hub.docker.com/r/lcy362/free-short-video)
@@ -194,7 +190,7 @@ Full walkthrough: [Getting Started → Configure API Key](docs/public/getting-st
194
190
  - **[Getting Started](docs/public/getting-started.md)** — Install and deploy in 4 ways: Manual (`start.sh`), Docker, npm (`npx free-short-video`), or AI-Agent assisted.
195
191
  - **[Usage Guide](docs/public/usage.md)** — Configure your API key, pick a video mode, resume from checkpoints, the three chaining modes, and logs & output layout.
196
192
  - **[Architecture](docs/public/architecture.md)** — Project structure and tech stack.
197
- - **[API Reference](docs/public/api.md)** — Full REST + WebSocket endpoint list.
193
+ - **[API Reference](docs/public/api.md)** — Full REST endpoint list (progress via polling, no WebSocket).
198
194
  - **[FAQ](docs/public/faq.md)** — Frequently asked questions.
199
195
  - **[About & License](docs/public/about.md)** — Acknowledgments and the MIT license.
200
196
 
@@ -16,6 +16,7 @@ import requests
16
16
 
17
17
  from core.api.error_collector import collect_error, collect_error_from_exception
18
18
  from core.api.rate_limiter import get_rate_limiter, request_with_key_rotation
19
+ from core.config import DEFAULT_TEXT_MODEL
19
20
 
20
21
  logger = logging.getLogger(__name__)
21
22
 
@@ -58,7 +59,7 @@ def strip_code_fence(text: str) -> str:
58
59
  class AgnesChatAPI:
59
60
  """Agnes LLM Chat API 封装(text + multimodal)。"""
60
61
 
61
- def __init__(self, api_key: str, model: str = "agnes-2.5-flash"):
62
+ def __init__(self, api_key: str, model: str = DEFAULT_TEXT_MODEL):
62
63
  self.api_key = api_key
63
64
  self.model = model
64
65
  # 基础 headers(不含 Authorization):每次请求前经 _auth_headers() 注入当前 Key
@@ -21,12 +21,12 @@ REQUEST_TIMEOUT = 20
21
21
 
22
22
  # 分组失败时的兜底列表
23
23
  _FALLBACK = {
24
- "text": [DEFAULT_TEXT_MODEL],
24
+ "text": [DEFAULT_TEXT_MODEL, "agnes-2.5-flash"],
25
25
  "image": [DEFAULT_IMAGE_MODEL, "agnes-image-2.1-flash", "agnes-image-2.0-flash"],
26
26
  "video": [DEFAULT_VIDEO_MODEL],
27
27
  }
28
28
 
29
- # 已废弃、应从可选列表中剔除的模型 ID(如 agnes-2.0-flash,官方已迁移至 agnes-2.5-flash)
29
+ # 已废弃、应从可选列表中剔除的模型 ID(如 agnes-2.0-flash,官方已迁移至新版本)
30
30
  _DEPRECATED_MODELS = {"agnes-2.0-flash"}
31
31
 
32
32
 
@@ -35,7 +35,7 @@ def _classify(model_id: str) -> str:
35
35
 
36
36
  - ``agnes-image*`` → image
37
37
  - ``agnes-video*`` → video
38
- - 其余(如 ``agnes-2.5-flash``)→ text
38
+ - 其余(如 ``agnes-3.0-flash``)→ text
39
39
  """
40
40
  if model_id.startswith("agnes-image"):
41
41
  return "image"
@@ -7,6 +7,7 @@ import itertools
7
7
  import json
8
8
  import logging
9
9
  import os
10
+ import re
10
11
  import subprocess
11
12
  from typing import List, Optional, Tuple
12
13
 
@@ -771,6 +772,69 @@ class AudioOverlayMixin:
771
772
  logger.warning(f"[Compositor] merge scene srts failed: {e}")
772
773
  return False
773
774
 
775
+ @staticmethod
776
+ def _probe_clip_duration(path: str, default: float = 5.0) -> float:
777
+ """探测媒体文件时长(秒),三级兜底。
778
+
779
+ 修复 Issue #54:Docker 镜像仅暴露 imageio-ffmpeg 自带的 ``ffmpeg``,
780
+ 不含 ``ffprobe``,``resolve_binary("ffprobe")`` 返回 ``None``;若直接传入
781
+ ``subprocess.run([None, ...])`` 会在 ``os.path.dirname(None)`` 处抛
782
+ ``TypeError``。此处按可用性依次尝试:
783
+
784
+ 1. ``ffprobe``(解析到真实路径时才用);
785
+ 2. ``ffmpeg -i`` 的 stderr ``Duration:`` 行(ffmpeg 为硬依赖,镜像必有);
786
+ 3. 调用方给定的 ``default``。
787
+
788
+ Args:
789
+ path: 媒体文件路径。
790
+ default: 全部探测失败时返回的兜底时长(秒)。
791
+
792
+ Returns:
793
+ 时长(秒);无法探测时返回 ``default``。
794
+ """
795
+ # 1) ffprobe(可能为 None,必须先判空)
796
+ ffprobe = resolve_binary("ffprobe")
797
+ if ffprobe:
798
+ try:
799
+ r = subprocess.run(
800
+ [ffprobe, "-v", "error", "-show_entries", "format=duration",
801
+ "-of", "csv=p=0", path],
802
+ stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=15,
803
+ )
804
+ val = float((r.stdout or "").strip() or 0)
805
+ if val > 0:
806
+ return val
807
+ except Exception as e:
808
+ logger.warning(f"[Compositor] ffprobe duration failed: {e}")
809
+
810
+ # 2) ffmpeg stderr 解析(ffmpeg 是硬依赖,Docker 镜像必有)
811
+ ffmpeg = resolve_binary("ffmpeg")
812
+ if ffmpeg:
813
+ try:
814
+ r = subprocess.run(
815
+ [ffmpeg, "-hide_banner", "-i", path],
816
+ stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=15,
817
+ )
818
+ m = re.search(
819
+ r"Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)", r.stderr or ""
820
+ )
821
+ if m:
822
+ h, mi, s = m.groups()
823
+ val = int(h) * 3600 + int(mi) * 60 + float(s)
824
+ if val > 0:
825
+ logger.info(
826
+ f"[Compositor] duration via ffmpeg fallback: {val:.2f}s ({path})"
827
+ )
828
+ return val
829
+ except Exception as e:
830
+ logger.warning(f"[Compositor] ffmpeg duration probe failed: {e}")
831
+
832
+ # 3) 默认兜底
833
+ logger.warning(
834
+ f"[Compositor] duration probe unavailable for {path}, using default {default}s"
835
+ )
836
+ return default
837
+
774
838
  @staticmethod
775
839
  def composite_anchor_video(
776
840
  clip_path: str,
@@ -810,14 +874,8 @@ class AudioOverlayMixin:
810
874
  )
811
875
  os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
812
876
 
813
- # Step 1: Get clip duration
814
- probe = subprocess.run(
815
- [resolve_binary("ffprobe"), "-v", "error", "-show_entries", "format=duration",
816
- "-of", "csv=p=0", clip_path],
817
- stdin=subprocess.DEVNULL,
818
- capture_output=True, text=True, timeout=15,
819
- )
820
- clip_duration = float(probe.stdout.strip() or 5.0)
877
+ # Step 1: Get clip duration(ffprobe 不可用时回退 ffmpeg,修复 Issue #54)
878
+ clip_duration = AudioOverlayMixin._probe_clip_duration(clip_path, default=5.0)
821
879
  if clip_duration <= 0:
822
880
  clip_duration = 5.0
823
881
 
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.4.4"
21
+ APP_VERSION = "6.4.6"
22
22
 
23
23
  # 未配置 API Key 时的统一报错文案(含免费获取与在线体验兜底,全站路由共用)
24
24
  API_KEY_MISSING_MSG = (
@@ -870,7 +870,7 @@ DURATION_FRAME_MAP = {
870
870
  # ═══════════════════════════════════════════════════
871
871
 
872
872
  # 各类型 Agnes 模型默认值(与三个 API 客户端的默认 model 对齐)
873
- DEFAULT_TEXT_MODEL = "agnes-2.5-flash"
873
+ DEFAULT_TEXT_MODEL = "agnes-3.0-flash"
874
874
  DEFAULT_IMAGE_MODEL = "agnes-image-2.5-flash"
875
875
  DEFAULT_VIDEO_MODEL = "agnes-video-v2.0"
876
876
 
@@ -18,6 +18,7 @@ from typing import Callable, Optional
18
18
  from core.api.agnes_image import AgnesImageAPI
19
19
  from core.api.agnes_video import AgnesVideoAPI, VideoTaskCancelled
20
20
  from core.compositor.concatenator import VideoConcatenator
21
+ from core.config import DEFAULT_TEXT_MODEL
21
22
  from core.pipelines import MultiScenePipeline
22
23
  from core.screenwriter import Screenwriter
23
24
  from models.task import (
@@ -75,7 +76,7 @@ class AnchorPipeline(MultiScenePipeline):
75
76
  api_key: str,
76
77
  task_id: str,
77
78
  dir_name: Optional[str] = None,
78
- chat_model: str = "agnes-2.5-flash",
79
+ chat_model: str = DEFAULT_TEXT_MODEL,
79
80
  image_model: str = "agnes-image-2.5-flash",
80
81
  video_model: str = "agnes-video-v2.0",
81
82
  progress_callback: Optional[Callable] = None,
@@ -8,6 +8,7 @@ from typing import Callable, Optional
8
8
 
9
9
  from core.api.agnes_image import AgnesImageAPI
10
10
  from core.api.agnes_video import AgnesVideoAPI
11
+ from core.config import DEFAULT_TEXT_MODEL
11
12
  from core.pipelines import MultiScenePipeline
12
13
  from core.screenwriter import Screenwriter
13
14
  from models.task import CreativeVideoTask
@@ -47,7 +48,7 @@ class CreativeVideoPipeline(
47
48
  api_key: str,
48
49
  task_id: str,
49
50
  dir_name: Optional[str] = None,
50
- chat_model: str = "agnes-2.5-flash",
51
+ chat_model: str = DEFAULT_TEXT_MODEL,
51
52
  image_model: str = "agnes-image-2.5-flash",
52
53
  video_model: str = "agnes-video-v2.0",
53
54
  progress_callback: Optional[Callable] = None,
@@ -19,6 +19,7 @@ from core.api.agnes_video import AgnesVideoAPI, VideoTaskCancelled
19
19
  from core.async_io import read_text
20
20
  from core.audio.voices import duration_len, estimate_chars_per_sec
21
21
  from core.compositor.concatenator import VideoConcatenator
22
+ from core.config import DEFAULT_TEXT_MODEL
22
23
  from core.pipelines import MultiScenePipeline
23
24
  from core.screenwriter import Screenwriter, is_prompt_language_explicit
24
25
  from models.task import (
@@ -144,7 +145,7 @@ class ManuscriptVideoPipeline(MultiScenePipeline):
144
145
  api_key: str,
145
146
  task_id: str,
146
147
  dir_name: str = None,
147
- chat_model: str = "agnes-2.5-flash",
148
+ chat_model: str = DEFAULT_TEXT_MODEL,
148
149
  image_model: str = "agnes-image-2.5-flash",
149
150
  video_model: str = "agnes-video-v2.0",
150
151
  progress_callback: Optional[Callable] = None,
@@ -19,6 +19,7 @@ from core.api.agnes_video import AgnesVideoAPI
19
19
  from core.audio.subtitle import SubtitleGenerator
20
20
  from core.audio.tts import SilentTTSEngine
21
21
  from core.compositor.concatenator import VideoConcatenator
22
+ from core.config import DEFAULT_TEXT_MODEL
22
23
  from core.pipelines import MultiScenePipeline
23
24
  from core.screenwriter import Screenwriter, clean_narration_text
24
25
  from models.task import (
@@ -73,7 +74,7 @@ class PoetryVideoPipeline(MultiScenePipeline):
73
74
  api_key: str,
74
75
  task_id: str,
75
76
  dir_name: Optional[str] = None,
76
- chat_model: str = "agnes-2.5-flash",
77
+ chat_model: str = DEFAULT_TEXT_MODEL,
77
78
  video_model: str = "agnes-video-v2.0",
78
79
  progress_callback: Optional[callable] = None,
79
80
  shutdown_event: Optional = None,
@@ -11,6 +11,7 @@ import traceback
11
11
  from typing import Callable, Optional
12
12
 
13
13
  from core.api.agnes_video import AgnesVideoAPI
14
+ from core.config import DEFAULT_TEXT_MODEL
14
15
  from core.pipelines import BasePipeline, PipelineShutdown
15
16
  from models.task import SimpleVideoTask, StepStatus
16
17
 
@@ -37,7 +38,7 @@ class SimpleVideoPipeline(BasePipeline):
37
38
  api_key: str,
38
39
  task_id: str,
39
40
  dir_name: str = None,
40
- chat_model: str = "agnes-2.5-flash",
41
+ chat_model: str = DEFAULT_TEXT_MODEL,
41
42
  image_model: str = "agnes-image-2.5-flash",
42
43
  video_model: str = "agnes-video-v2.0",
43
44
  progress_callback: Optional[Callable] = None,
@@ -26,7 +26,7 @@ logger = logging.getLogger(__name__)
26
26
  # "zh" — 所有 meta-prompt 使用中文(默认)
27
27
  # "en" — 所有 meta-prompt 使用英文
28
28
  # 示例:export PROMPT_LANGUAGE=en
29
- from core.config import get_settings as _get_settings # noqa: I001 就地导入避免循环依赖
29
+ from core.config import DEFAULT_TEXT_MODEL, get_settings as _get_settings # noqa: I001 就地导入避免循环依赖
30
30
  PROMPT_LANGUAGE = _get_settings().prompt_language
31
31
 
32
32
 
@@ -112,7 +112,7 @@ class Screenwriter(
112
112
  对外接口与拆分前完全一致(mixin 组合,方法经 MRO 解析)。
113
113
  """
114
114
 
115
- def __init__(self, api_key: str, model: str = "agnes-2.5-flash", language: str = None):
115
+ def __init__(self, api_key: str, model: str = DEFAULT_TEXT_MODEL, language: str = None):
116
116
  self.api_key = api_key
117
117
  self.model = model
118
118
  self.language = language if language else PROMPT_LANGUAGE # "zh" 中文 / "en" 英文
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-short-video",
3
- "version": "6.4.4",
3
+ "version": "6.4.6",
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",